How to reload data after all Firebase calls finished? - ios

I'm using Firebase (Swift) to read a list of group ids that the user belongs to then looping over the ids to get more info about the groups. Something similar to this (pseudo code):
// List the names of all Mary's groups
var ref = new Firebase("https://docs-examples.firebaseio.com/web/org");
// fetch a list of Mary's groups
ref.child("users/mchen/groups").on('child_added', function(snapshot) {
// for each group, fetch the name and print it
String groupKey = snapshot.key();
ref.child("groups/" + groupKey + "/name").once('value', function(snapshot) {
System.out.println("Mary is a member of this group: " + snapshot.val());
});
});
How do I know that all Firebase observeSingleEvent has done executing so I could reload the data in my collection view.
Edit:
After doing more research, this looks very similar to this question. I could use dispatch_group or Bolts framework
Edit 2:
Thanks to #appzYourLife for his answer. I also was able to solve it using RxSwift. I simply wrapped the Firebase calls with observers and saved them in an array then called
Observable.zip(observables, { _ in }).subscribe(onCompleted: {
self.contentView.collection.reloadData() // do something here
})

If you want to be notified when all the firebase calls have been completed you can use this code
let ref = FIRDatabase.database().reference()
ref.child("users/mchen/groups").observeSingleEvent(of: .value, with: { snapshot in
let groupKeys = snapshot.children.flatMap { $0 as? FIRDataSnapshot }.map { $0.key }
// This group will keep track of the number of blocks still pending
let group = DispatchGroup()
for groupKey in groupKeys {
group.enter()
ref.child("groups").child(groupKey).child("name").observeSingleEvent(of: .value, with: { snapshot in
print("Mary is a member of this group: \(snapshot.value)")
group.leave()
})
}
// We ask to be notified when every block left the group
group.notify(queue: .main) {
print("All callbacks are completed")
}
})
How does it work?
There are 4 main instructions involved.
First of all we create a group DispatchGroup(). This value will keep track of the number of pending blocks.
let group = DispatchGroup()
Then before starting a new asynchronous call we tell the group there is a new pending block.
group.enter()
Inside the callback closure we tell the group that one block has finished its work.
group.leave()
We tell the block to run a closure when the number of blocks into the group does become zero.
group.notify(queue: .main) {
print("All callbacks are completed")
}

You have the list of groups, so you can store the count in one Int object. Lets say totalCount.
Then take another object.
Lets say counter.
Then in each completion handler .
After the print statement
ref.child("groups/" + groupKey + "/name").once('value', function(snapshot)
{
System.out.println("Mary is a member of this group: " + snapshot.val());
if counter == count
{
collectionView.reload();
}
else
{
counter += 1;
}
});

Related

Get values from two apis into two different Observable and perform some operation

I have two independent observables. I need to perform some operation when both of them are complete and each of them provided an array.
let myObj1Array = myObj1Manager.getMyObj1List()//returns Observable<[MyObj1]>
let myObj2Array = myObj2Manager.getMyObj2List()//returns Observable<[MyObj2]>
Now I need to compare values of myObj1Array and myObj2Array and on the basis of that create another array using values from both arrays. I know how to subscribe 1 variable but not sure how to observe completion of two different arrays.
Edit:
I also tried following but I get values only from first array:
let myObj1Array = myObj1Manager.getMyObj1List()
let myObj2Array = myObj1Array.flatMap { _ in myObj2Manager.getMyObj2List() }
Observable.combineLatest(myObj1Array, myObj2Array)
.subscribe(onNext: { (sss, sds) in
print(sss)
})
.addDisposableTo(disposeBag)
I am actually kind of clueless about how to handle such scenario.
Edit2:
function to get the observables in first array:
func getMyObj1List() -> Observable<[MyObj1]> {
return Observable.create { observer -> Disposable in
self.specialsRest.getMyObj1List { response, error in
if let error = error {
observer.onError(Exception(error))
return
}
guard let saleItems = MyObj1.decode(data: response?.data) else {
observer.onError(Exception("Could not decode specials!"))
return
}
queueBackground.async {
observer.onNext(saleItems)
observer.onCompleted()
}
}
return Disposables.create { self.specialsRest.cancel() }
}
}
DispatchGroup is probably the way to go here.
https://developer.apple.com/documentation/dispatch/dispatchgroup
When all work items finish executing, the group executes its completion handler. You can also wait synchronously for all tasks in the group to finish executing.
var dg:DispatchGroup = DispatchGroup()
//Wherever you start your observables.
//Start Observer1
dg.enter()
//Start Observer2
dg.enter()
...
...
...
//Wherever you retrieve data
SomeAsyncFuncForObserver1 {
//Get Data
dg.leave()
}
SomeAsyncFuncForObserver2 {
//Get Data
dg.leave()
}
dg.notify(queue: .main) {
print("all finished.")
}
I believe you need to use zip instead of combineLatest. From the docs
The CombineLatest operator behaves in a similar way to Zip, but while
Zip emits items only when each of the zipped source Observables have
emitted a previously unzipped item, CombineLatest emits an item
whenever any of the source Observables emits an item (so long as each
of the source Observables has emitted at least one item).
Observable
.zip(myObj1Array, myObj2Array)
.subscribe(onNext: { (sss, sds) in
print(sss)
})
.addDisposableTo(disposeBag)

How does control flow work when retrieving Information from Firebase?

var ergebnisBluetezeit = Set<String>()
let refBluetezeit = rootRef.child("Pflanzen").child("Eigenschaften").child("Blütezeit")
refBluetezeit.child("Februar").observeSingleEvent(of: .value, with: { snapshot in
for plant in snapshot.children {
self.ergebnisBluetezeit.insert((plant as AnyObject).value)
}
})
print(ergebnisBluetezeit)
I want to retrieve Data from my Firebase Database. The Retrieving Process does work already, but the following confuses me: the current output from the print is an empty set, but when i use the var ergebnisBluetezeit elsewhere (for example setup a button, which action is to print ergebnisBluetezeit), it is filled. When i put the print in the for loop, it does print the right output, too.
I seem to not have understood the control flow here, so my Question:
How can i use the Set where the print statement is at the moment?
Thanks for your help.
It's the logic of asynchronous calls
print("1") // empty
refBluetezeit.child("Februar").observeSingleEvent(of: .value, with: { snapshot in
print("3") // empty
for plant in snapshot.children {
self.ergebnisBluetezeit.insert((plant as AnyObject).value)
}
print(ergebnisBluetezeit) // not empty
})
print("2") // empty
the value is empty until the request finishes regardless of where in code ordering you run the print , as the numbering above in order 1 , 2 , 3 to know when it finishes you can use completions like
func getData(completion:#escaping() -> ()) {
let refBluetezeit = rootRef.child("Pflanzen").child("Eigenschaften").child("Blütezeit")
refBluetezeit.child("Februar").observeSingleEvent(of: .value, with: { snapshot in
for plant in snapshot.children {
self.ergebnisBluetezeit.insert((plant as AnyObject).value)
}
completion()
})
}
And call
getData {
print(ergebnisBluetezeit)
}

Swift 3 Firebase - Return All Users That Have An ID From An Array of IDs

Let's say in the front end I have an array of user IDs, pulled from another data source, I.E.:
let userIdArray = ["123456", "123457", "123458", "123459"]
And I need to query my Firebase database, returning all users that have one of the IDs in that array. How would I go about doing this? Do I have to create a new query for each ID? That would potentially be overly taxing, as the number of users in the array could be as many as 20-30 IDs.
The following code will loop through ever userID at the fastest way, since it does not wait until 1 download has finished. I created a dispatch group and I enter the group before downloading the data, and leave the group when the data is finished downloading. After that, group.notify will notify you when the loop is finished.
func getUserIds() {
let userIdArray = ["123456", "123457", "123458", "123459"]
let group = DispatchGroup()
for singleUser in userIdArray{
group.enter()
ref.child("insertpath/\(singleUser)").observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
group.leave()
}) { (error) in
print(error.localizedDescription)
group.leave()
}
}
group.notify(queue: .main) {
print("loop finished")
}
}
}

How to retrieve data synchronously from Firebase?

I have two collections namely, Users and Questions.
Based on the user logged in using userId, I retrieve the currQuestion value from users collection.
Based on the currQuestion value, I need to retrieve the question document from Firebase Questions collection.
I've used the below code to retrieve userId
rootRef.child("0").child("users")
.queryOrderedByChild("userId")
.queryEqualToValue("578ab1a0e9c2389b23a0e870")
.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
for child in snapshot.children {
self.currQuestion = child.value["currentQuestion"] as! Int
}
print("Current Question is \(self.currQuestion)")
//print(snapshot.value as! Array<AnyObject>)
}, withCancelBlock : { error in
print(error.description)
})
and to retrieve question
rootRef.child("0").child("questions")
.queryOrderedByChild("id")
.queryEqualToValue(currQuestion)
.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
for child in snapshot.children {
print(child.value["question"] as! String)
}
}, withCancelBlock: { error in
print(error.description)
})
But the above code executes asynchronously. I need to solution to make this synchronous or how to implement listeners so I can fire back the question query once the currQuestion value is changed?
Write your own method which takes in a completion handler as its parameter and waits for that block of code to finish. Like so:
func someMethod(completion: (Bool) -> ()){
rootRef.child("0").child("users")
.queryOrderedByChild("userId")
.queryEqualToValue("578ab1a0e9c2389b23a0e870")
.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
for child in snapshot.children {
self.currQuestion = child.value["currentQuestion"] as! Int
}
print("Current Question is \(self.currQuestion)")
completion(true)
//print(snapshot.value as! Array<AnyObject>)
}, withCancelBlock : { error in
print(error.description)
})
}
And then whenever you want to call that function, call like so:
someMethod{ success in
if success{
//Here currValue is updated. Do what you want.
}
else{
//It is not updated and some error occurred. Do what you want.
}
}
Completion handlers are usually used to wait for a block of code to finish executing completely. P.S. As long as they don't block the main thread, asynchronous requests are made to act synchronous by adding a completion handler like the code shown above.
What it simply does is wait for your currValue to be updated first (receiving the data async from the server) and then when you call someMethod like how I've shown, and since the last and only parameter to the function someMethod is a closure (a.k.a, trailing Closure ), you can skip the parenthesis and call it. Here is a good read about closures. And since the closure is of type (Bool) -> (), you just tell your someMethod when the task is completed which is done like completion(true) in my code, and then while calling it, you call it with success (You can use any word you want) which WILL BE of type Bool as it is declared like so, And then use it in the function call. Hope it helps. :)

Checking for multiple asynchronous responses from Alamofire and Swift

I am writing an application that depends on data from various sites/service, and involves performing calculations based on data from these different sources to produce an end product.
I have written an example class with two functions below that gathers data from the two sources. I have chosen to make the functions different, because sometimes we apply different authentication methods depending on the source, but in this example I have just stripped them down to their simplest form. Both of the functions use Alamofire to fire off and handle the requests.
I then have an initialisation function, which says if we have successfully gathered data from both sources, then load another nib file, otherwise wait up to for seconds, if no response has been returned, then load a server error nib file.
I've tried to make this example as simple as possible. Essentially. This is the kind of logic I would like to follow. Unfortunately it appears this does not currently work in its current implementation.
import Foundation
class GrabData{
var data_source_1:String?
var data_source_2:String?
init(){
// get data from source 1
get_data_1{ data_source_1 in
println("\(data_source_1)")
}
// get data from source 2
get_data_2{ data_source_1 in
println("\(data_source_1)")
}
var timer = 0;
while(timer<5){
if((data_source_1 == nil) && (data_source_2 == nil)){
// do nothing unless 4 seconds has elapsed
if (timer == 4){
// load server error nib
}
}else{
// load another nib, and start manipulating data
}
// sleep for 1 second
sleep(1)
timer = timer+1
}
}
func get_data_1(completionHandler: (String) -> ()) -> () {
if let datasource1 = self.data_source_1{
completionHandler(datasource1)
}else{
var url = "http://somewebsite.com"
Manager.sharedInstance.request(.GET, url).responseString {
(request, response, returnedstring, error) in
println("getting data from source 1")
let datasource1 = returnedstring
self.data_source_1 = datasource1
completionHandler(datasource1!)
}
}
}
func get_data_2(completionHandler: (String) -> ()) -> () {
if let datasource2 = self.data_source_2{
completionHandler(datasource2)
}else{
var url = "http://anotherwebsite.com"
Manager.sharedInstance.request(.GET, url).responseString {
(request, response, returnedstring, error) in
println("getting data from source 2")
let datasource2 = returnedstring
self.data_source_2 = datasource2
completionHandler(datasource2!)
}
}
}
}
I know that i could put the second closure within the first inside the init function, however, I don't think this would be best practice and I am actually pulling from more than 2 sources, so the closure would be n closures deep.
Any help to figuring out the best way to checking if multiple data sources gave a valid response, and handling that appropriately would be much appreciated.
Better than that looping process, which would block the thread, you could use dispatch group to keep track of when the requests were done. So "enter" the group before issuing each of the requests, "leave" the group when the request is done, and set up a "notify" block/closure that will be called when all of the group's tasks are done.
For example, in Swift 3:
let group = DispatchGroup()
group.enter()
retrieveDataFromURL(url1, parameters: firstParameters) {
group.leave()
}
group.enter()
retrieveDataFromURL(url2, parameters: secondParameters) {
group.leave()
}
group.notify(queue: .main) {
print("both requests done")
}
Or, in Swift 2:
let group = dispatch_group_create()
dispatch_group_enter(group)
retrieveDataFromURL(url1, parameters: firstParameters) {
dispatch_group_leave(group)
}
dispatch_group_enter(group)
retrieveDataFromURL(url2, parameters: secondParameters) {
dispatch_group_leave(group)
}
dispatch_group_notify(group, dispatch_get_main_queue()) {
print("both requests done")
}
The other approach is to wrap these requests within an asynchronous NSOperation subclass (making them cancelable, giving you control over constraining the degree of concurrency, etc.), but that's more complicated, so you might want to start with dispatch groups as shown above.

Resources