Evan
Evan

Reputation: 2040

Working With Async Firebase Calls SwiftUI

I understand that the Firebase getDocument call is Async, so I'm trying to figure out how to essentially wait until the call finishes executing, and then move on to doing other stuff.

I have tried making use of DispatchGroup() and entering/leaving the group, but I can't seem to get it to work correctly. I have something like the following:

let myGroup = DispatchGroup()
let usersRef = self.db.collection("Users").document("Users").collection("Users")
if self.testCondition == false {
    self.errorMessage = "error"
} else{
    usersRef.getDocuments {(snap, err) in
        myGroup.enter()
        //basically getting every username
        for document in snap!.documents{
            let user = document["username"] as! String
            let userRef = usersRef.document(user)
            userRef.getDocument { (snapshot, err) in
                if err != nil {
                    print(err)
                } else {
                    let sample = snapshot!["sample"] as! String
                    if sample == 'bad' {
                        self.errorMessage = "error"
                    }
                }
            }
        }
        myGroup.leave()
    }
    print("what4")
    //I would like it so that I can execute everything in a code block like this
    //after the async call finishes
    myGroup.notify(queue: .main) {
        print("Finished all requests.")
        //THEN DO MORE STUFF
    }
}

How can I modify the placement myGroup.enter() and myGroup.leave() in this so that, after the Firebase call has finished, I can continue executing code?

Thanks!

Upvotes: 2

Views: 1347

Answers (1)

Simon
Simon

Reputation: 1850

This explains the DispatchGroup() a little bit.

You just have one litte mistake in your code then it should be working. Make sure to enter() the group outside of the Firebase getDocuments() call. As this already makes the request and takes time thus the process will continue.

This little simple example should help you understand it:

func dispatchGroupExample() {
        
        // Initialize the DispatchGroup
        let group = DispatchGroup()
        
        print("starting")
        
        // Enter the group outside of the getDocuments call
        group.enter()
        
        let db = Firestore.firestore()
        let docRef = db.collection("test")
        docRef.getDocuments { (snapshots, error) in
            
            if let documents = snapshots?.documents {
                for doc in documents {
                    print(doc["name"])
                }
            }
            
            // leave the group when done
            group.leave()
        }
        
        // Continue in here when done above
        group.notify(queue: DispatchQueue.global(qos: .background)) {
            print("all names returned, we can continue")
        }
    }

When waiting for multiple asynchronous calls use completing in the asynchronous function which you let return as soon as you leave the group. Full eg. below:

class Test {
    
    init() {
        self.twoNestedAsync()
    }
    
    func twoNestedAsync() {
        let group = DispatchGroup() // Init DispatchGroup
        
        // First Enter
        group.enter()
        print("calling first asynch")
        
        self.dispatchGroupExample() { isSucceeded in
            
            // Only leave when dispatchGroup returns the escaping bool
            
            if isSucceeded {
                group.leave()
            } else {
                // returned false
                group.leave()
            }
        }
        
        // Enter second
        group.enter()
        print("calling second asynch")
        
        self.waitAndReturn(){ isSucceeded in
            
            // Only return once the escaping bool comes back
            if isSucceeded {
                group.leave()
            } else {
                //returned false
                group.leave()
            }
            
        }
        
        group.notify(queue: .main) {
            print("all asynch done")
        }
    }
    
    // Now added escaping bool which gets returned when done
    func dispatchGroupExample(completing: @escaping (Bool) -> Void) {
        
        // Initialize the DispatchGroup
        let group = DispatchGroup()
        
        print("starting")
        
        // Enter the group outside of the getDocuments call
        group.enter()
        
        let db = Firestore.firestore()
        let docRef = db.collection("test")
        docRef.getDocuments { (snapshots, error) in
            
            if let documents = snapshots?.documents {
                for doc in documents {
                    print(doc["name"])
                }

                // leave the group when succesful and done
                group.leave()
            }
            
            if let error = error {
                // make sure to handle this
                completing(false)
                group.leave()
            }
        }
        
        // Continue in here when done above
        group.notify(queue: DispatchQueue.global(qos: .background)) {
            print("all names returned, we can continue")
            
            //send escaping bool.
            completing(true)
        }
    }
    
    func waitAndReturn(completing: @escaping (Bool) -> Void) {
        DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2), execute: {
            print("Done waiting for 2 seconds")
            completing(true)
        })
    }
}

This gives us the following output:

DispatchGroup with completing and escaping output

Upvotes: 5

Related Questions