I have several functions to retrieve data from firebase. I want an activity indicator to spin until all the data is retrieved.
My problem is that, the functions aren't even started. To search for the mistake I've entered print statements which aren't called.
This is my code:
override func viewDidLoad() {
super.viewDidLoad()
loadingActInd.hidesWhenStopped = true
self.loadingActInd.startAnimating()
let group = dispatch_group_create()
dispatch_group_enter(group)
func loadTweetComplete() {
dispatch_group_leave(group)
}
dispatch_group_enter(group)
func loadEventsComplete() {
dispatch_group_leave(group)
}
dispatch_group_notify(group, dispatch_get_main_queue()) {
self.loadingActInd.stopAnimating()
print("deejayTweetsDictionary = \(deejayTweetsDictionary)")
print("finished executing")
}
}
func loadTweetComplete(completionHandler: () -> ()) {
print("TEST")
deejayTweetsDictionary.removeAll()
let usersRef = firebase.child("DeejayTweets").child(passedDJ.objectId)
usersRef.observeSingleEventOfType(.Value, withBlock: { snapshot in
if snapshot.exists() {
deejayTweetsDictionary.removeAll()
let sorted = (snapshot.value!.allValues as NSArray).sortedArrayUsingDescriptors([NSSortDescriptor(key: "date",ascending: false)])
for element in sorted {
deejayTweetsDictionary.append(element as! NSMutableDictionary)
}
}
completionHandler()
})
}
func loadEventsComplete(completionHandler: () -> ()) {
print("TEST")
eventDates.removeAll()
if passedDJ.objectId != nil {
let userRef = firebase.child("djBookings").child(passedDJ.objectId)
userRef.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
if snapshot.exists() {
eventDates.removeAll()
let item = (snapshot.value as? NSMutableDictionary)!
let allValues = item.allValues
for element in allValues {
eventDates.append(element as! NSMutableDictionary)
}
}
completionHandler()
})
}
}
The Indicator spins forever and not even the print("TEST") statements are called. What am I doing wrong? Help is very appreciated.
func loadTweetComplete() {
dispatch_group_leave(group)
}
defines a (nested) function. What you want is to call the
function with the given completion handler as an argument.
Using the trailing closure syntax that would be
loadTweetComplete {
dispatch_group_leave(group)
}
Related
Hi i'm just getting started with RxSwift and decided to make simple Currency Exchange application. My app has two view's (allCurrenciesList and userFavouritesView). Basically all logic works, but only if i run networking func every single time one of view didAppear/didLoad. My point is two fetch it only once, and received many times, when necessary. Application fetch dictionary of currencies and in ViewModel pass it to BehaviorSubject, and when view being load/appear it just subscribe it, and use it in UITableView. Thanks
class ListViewModel {
let service: CurrencyService!
var curriencies = BehaviorRelay<[Currency]>(value: [])
var currienciesObservable: Observable<[Currency]> {
return curriencies.asObservable().share()
}
let disposeBag = DisposeBag()
init(service: CurrencyService) {
self.service = service
}
func fetchAllCurrencies() {
self.service.fetchAllSymbols { result in
switch result{
case .success(let currencies):
self.dictionaryIntoArray(currencies: currencies["symbols"] as! [String : Any])
case .failure:
print("error")
}
}
}
private func dictionaryIntoArray(currencies: [String: Any]) {
var currencyArray = [Currency]()
for (symbol, name) in currencies {
currencyArray.append(Currency(symbol: symbol, fullName: name as! String))
}
let sortedArray = currencyArray.sorted { $0.fullName < $1.fullName }
self.curriencies.accept(sortedArray)
}
allCurrenciesList
override func viewDidLoad() {
super.viewDidLoad()
setupView()
configureTableViewDataSource()
tableView.delegate = self
fetchData()
}
private func fetchData() {
viewModel.fetchAllCurrencies() // this func is necceserry everysingle time
viewModel.currienciesObservable.subscribe(onNext: { curriencies in
self.applySnapshot(curriencies: curriencies)
}).disposed(by: disposeBag)
}
userFavouritesView
override func viewDidLoad() {
super.viewDidLoad()
viewModel.fetchAllCurrencies() // this func is necceserry everysingle time
viewModel.currienciesObservable.subscribe(onNext: { allCurencies in
let selectedItems = UserDefaults.standard.array(forKey: "SelectedCells") as? [Int] ?? [Int]()
var currenciesArray: [Currency] = []
selectedItems.forEach { int in
self.pickerValues.append(allCurencies[int])
currenciesArray.append(allCurencies[int])
}
self.applySnapshot(curriencies: currenciesArray)
}).disposed(by: disposeBag)
}
The key here is to not use a Subject. They aren't recommended for regular use. Just define the currienciesObservable directly.
Something like this:
class ListViewModel {
let currienciesObservable: Observable<[Currency]>
init(service: CurrencyService) {
self.currienciesObservable = service.rx_fetchAllSymbols()
.map { currencies in
currencies["symbols"]?.map { Currency(symbol: $0.key, fullName: $0.value as! String) }
.sorted(by: { $0.fullName < $1.fullName }) ?? []
}
}
}
extension CurrencyService {
func rx_fetchAllSymbols() -> Observable<[String: [String: Any]]> {
Observable.create { observer in
self.fetchAllSymbols { result in
switch result {
case let .success(currencies):
observer.onNext(currencies)
observer.onCompleted()
case let .failure(error):
observer.onError(error)
}
}
return Disposables.create()
}
}
}
With the above, every time you subscribe to the currenciesObservable the fetch will be called.
As I understand, it's because your fetchAllSymbols function was not stored in the DisposeBag.
func fetchAllCurrencies() {
self.service.fetchAllSymbols { result in
switch result{
case .success(let currencies):
self.dictionaryIntoArray(currencies: currencies["symbols"] as! [String : Any])
case .failure:
print("error")
}
}.dispose(by: disposeBag)
}
I have the following function in a separate swift file which I use to make Firebase calls for data:
func fetchPosts(data: inout [Post]) {
postRef.observe(DataEventType.value) { (snapshot) in
data.removeAll() // ***Error thrown here***
if snapshot.value is NSNull {
} else {
var counter: UInt = 0
for item in snapshot.children {
let userID = (item as! DataSnapshot).key
self.postRef.child(userID).observe(DataEventType.value) { (snap) in
counter = counter + 1
for child in snap.children {
if let snapshot = child as? DataSnapshot,
let post = Post(snapshot: snapshot) {
data.append(post) // ***Error thrown here***
}
if (counter == snapshot.childrenCount) {
data = data.sorted(by: { $0.id > $1.id }) // ***Error thrown here***
}
}
}
}
}
}
}
In my view I have the following:
#State var posts: [Post] = [] // which is the place holder for the posts data
and then i have the following call
func fetchPosts() {
postStore.fetchPosts(data: &posts)
}
Which calls the function above and passes the [Post] array by reference
My issue is that I get the following error Escaping closure captures 'inout' parameter 'data' in the above function and I can not figure out what I need to do to resolve it!?
Has anyone ever encountered this error and what do you do to resolve it!?
That won't work with #State, instead you have to use completion callback pattern, like
func fetchPosts() {
self.posts = [] // reset
postStore.fetchPosts() { newPosts in
DispatchQueue.main.async {
self.posts = newPosts
}
}
}
and fetching function
func fetchPosts(completion: #escaping ([Post]) -> () ) {
// ...
var data: [Post] = [] // data is a local variable
// ...
if (counter == snapshot.childrenCount) {
completion(data.sorted(by: { $0.id > $1.id }))
}
// ...
}
This is my enum, which is declared globally in my home view controller (Before class HomeViewController: UIViewController,)
enum HomeVCSectionTypes: String, CaseIterable {
case mostPopularBoolValue = "mostPopularBoolValue"
case discountedBoolValue = "discountedBoolValue"
case newlyAddedBoolValue = "newlyAddedBoolValue"
init?(id : Int) {
switch id {
case 1: self = .mostPopularBoolValue
case 2: self = .discountedBoolValue
case 3: self = .newlyAddedBoolValue
default: return nil
}
}
}
And I am iterating through it in my view will appear like this:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
for sectionType in HomeVCSectionTypes.allCases {
fetchData(homeVCSectionTypes: sectionType)
}
}
But it is not showing the values according to their serial indexes. What am I missing here?
func fetchData(homeVCSectionTypes: HomeVCSectionTypes) {
self.activityIndicator.startAnimating()
objectArray.removeAll()
let semaphore = DispatchSemaphore(value: 0)
let dispatchQueue = DispatchQueue.global(qos: .background)
dispatchQueue.async {
let docRef = Firestore.firestore().collection("album").order(by: "timestamp", descending: true).whereField(homeVCSectionTypes.rawValue, isEqualTo: true).limit(to: 10)
docRef.getDocuments { (snapshot, error) in
guard let snapshot = snapshot else { return }
var items = [ProductCategoryAlbum]()
if snapshot.documents.count > 0 {
do {
for document in snapshot.documents {
let object = try document.decode(as: ProductCategoryAlbum.self)
items.append(object)
}
self.objectArray.append(HomeObject(sectionName: homeVCSectionTypes.rawValue, sectionObjects: items))
} catch {
print(error)
}
DispatchQueue.main.async {
self.homeTableView.reloadData()
self.activityIndicator.stopAnimating()
}
} else {
DispatchQueue.main.async {
self.homeTableView.isHidden = true
self.noDataLabel.isHidden = false
self.activityIndicator.stopAnimating()
}
}
semaphore.signal()
}
semaphore.wait()
}
}
I am trying to save my Firebase Data in an array but the array count is everytime 0.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
getAllSkateShops()
}
func getAllSkateShops() {
ref = FIRDatabase.database().reference()
ref.child("Shops").observeSingleEvent(of: .value, with: { (snapshot) in
//var newShops: [SkateShop] = []
for item in snapshot.children {
let skateShopItem = SkateShop(snapshot: item as! FIRDataSnapshot)
self.shops.append(skateShopItem)
DispatchQueue.main.async(execute: {
print("OBSERVE SHOP COUNT: \(self.shops.count)")
})
}
})
}
And in the function viewDidLoad() is self.shop.count is zero but I need this array with all Shops.
I hope anybody can help me ;)
I had the same problem, idk why this works but in DispatchQueue.main.async do (edited):
func getAllSkateShops() {
ref = FIRDatabase.database().reference()
ref.child("Shops").observeSingleEvent(of: .value, with: { (snapshot) in
//var newShops: [SkateShop] = []
for item in snapshot.children {
let skateShopItem = SkateShop(snapshot: item as! FIRDataSnapshot)
self.shops.append(skateShopItem)
self.shops2.append(skateShopItem)
DispatchQueue.main.async(execute: {
var temporaryArray = self.shop2
self.shop = temporaryArray
})
}
})
}
If that doesn't work comment, I'll give another option that might work.
I have MainViewController that is my UIPageViewController. Ther are 2 views FirstDataViewController and SecondDataViewController that i want to show.
I want to execute my request that updates items in MainViewControllre and then pass it to my 2 child views.
getData is my function that contains data from Request and then updates MainViewController's items
extension MainViewController {
func getData() {
getBasicData() { [weak self] (basicModel) in
guard let strongSelf = self else { return }
strongSelf.getExperienceData() { [weak self] (experienceModel) in
guard let strongSelf = self else { return }
let items = RequestItems(basicData: basicModel,
experienceData: experienceModel)
strongSelf.updateItems(items: items)
}
}
}
}
MainViewController:
class MainViewController: UIPageViewController {
var items: RequestItems
let firstDataViewController: FirstDataViewController
let secondDataViewController: SecondDataViewController
let basicDataManager: APIManagerProtocol
let experienceDataManager: APIManagerProtocol
private(set) lazy var orderedViewControllers: [UIViewController] = {
return [self.firstDataViewController, self.secondDataViewController]
}()
convenience init() {
self.init(with: RequestItems())
}
init(with items: RequestItems) {
self.items = items
let apiManager = APIManager()
basicDataManager = apiManager.createBasicDataManager()
experienceDataManager = apiManager.createExperienceDataManager()
self.firstDataViewController = FirstDataViewController(with: items)
self.secondDataViewController = SecondDataViewController(with: items)
super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
self.edgesForExtendedLayout = []
}
func updateItems(items: RequestItems) {
self.items = items
}
}
How to use getData() function to update MainViewController items first and then pass data to child views? Or maybe there is just better option to do this?
It's not a good solution. "GetData" method needs some time to execute completely so you should:
Execute "getData" before load MainViewController, then pass "request items" to it and update yours views.
But I assume that your MainViewController is first controller in your application so you should:
Add completion block to your "getData" method:
func getData(_ completion: [RequestItems] -> ()) {
getBasicData() { [weak self] (basicModel) in
guard let strongSelf = self else { return }
strongSelf.getExperienceData() { [weak self] (experienceModel) in
guard let strongSelf = self else { return }
let items = RequestItems(basicData: basicModel,
experienceData: experienceModel)
completion(items)
}
}
}
Add "updateItems" method to yours view controllers (instead of passing it in init method)
Call your "getData" method with completion handler but outside of MainViewController init
init(with items: RequestItems) {
...
self.items = items
self.firstDataViewController = FirstDataViewController()
self.secondDataViewController = SecondDataViewController()
...
}
func viewDidLoad() {
super.viewDidLoad()
getBasicData { [weak self] (items) in
guard let strongSelf = self else { return }
strongSelf.updateItems(items)
strongSelf.firstDataViewController.updateItems(with: items)
strongSelf.secondDataViewController.updateItems(with: items)
}
}
func updateItems(items: RequestItems) {
self.items = items
}