0

I'm calling a Firestore query that does come back, but I need to ensure completion before moving on with the rest of the code. So I need a completion handler...but for the life of me I can't seem to code it.

As advised by comments I have tried to use the async / await calls: function:

// get user info from db
    func getUser() async {
        self.db.collection("userSetting").getDocuments() { (querySnapshot, err) in
            if let err = err {
                print("Error getting documents: \(err)")
            } else {
                for document in querySnapshot!.documents {
                    let userTrust = document.data()["userTrust"] as! String
                    let userGrade = document.data()["userGrade"] as! String
                    let userDisclaimer = document.data()["userDisclaimer"] as! String
                    
                    var row = [String]()
                    row.append(userTrust)
                    row.append(userGrade)
                    row.append(userDisclaimer)
                    
                    self.userArray.append(row)
                    
                    // set google firebase analytics user info
                    self.userTrustInfo = userTrust
                    self.userGradeInfo = userGrade

                }
            }
            
        }
    }

Called by:

internal func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        FirebaseApp.configure()
        db = Firestore.firestore()
        Database.database().isPersistenceEnabled = true

        Task {
            do {
                let userInfo = await getUser()
            }
        } return true }

I used a Task as didFinishLauncingWithOptions is synchronous and not asynchronous

However, the getUser() still isn't completed before didFinishLauncingWithOptions moves on.

I need the data from getUser as the very next step uses the data in the array, and without it I get an 'out of bounds exception' as the array is still empty.

Also tried using dispatch group within the func getUser(). Again with no joy.

Finally tried a completion handler:

func getUser(completion: @escaping (Bool) -> Void) {

        self.db.collection("userSetting").getDocuments() { (querySnapshot, err) in
            if let err = err {
                print("Error getting documents: \(err)")
            } else {

                for document in querySnapshot!.documents {

                    let userTrust = document.data()["userTrust"] as! String
                    let userGrade = document.data()["userGrade"] as! String
                    let userDisclaimer = document.data()["userDisclaimer"] as! String
                    
                    var row = [String]()
                    row.append(userTrust)
                    row.append(userGrade)
                    row.append(userDisclaimer)
                    
                    self.userArray.append(row)
                    
                    // set google firebase analytics user info
                    self.userTrustInfo = userTrust
                    self.userGradeInfo = userGrade
                    
                    completion(true)
                    }
                
                }
            }
        
        }

Nothing works. The getUser call isn't completed before the code moves on. Can someone please help. I've searched multiple times, looked at all linked answers but I can not make this work.I'm clearly missing something easy, please help

Nicholas Farmer
  • 773
  • 7
  • 32
  • https://stackoverflow.com/questions/73429625/how-to-infer-a-generic-paramater-with-an-async-await-function/73430225#73430225 – lorem ipsum Jan 29 '23 at 23:44
  • Thanks, but would really appreciate advice directly with my code. I just can’t seem to create a handler sl to work on my code no matter how many other examples I’m seeing – Nicholas Farmer Jan 29 '23 at 23:49
  • That is a different approach it uses async await. Much simpler – lorem ipsum Jan 29 '23 at 23:51
  • Thank you, again, could you please show me an example with my code? – Nicholas Farmer Jan 29 '23 at 23:57
  • You haven't shown what part is giving you issues, it sounds like you just want someone to write the code for you. SO isn't a code writing service. – lorem ipsum Jan 30 '23 at 00:00
  • Never said it was. The issue I’m having is I can’t seem to see how to add a completion handler (or your advice on async) to my code. I can’t seem to see even how to start. Hence asking for advice specific to my code so I can then learn with code I’m familiar with. – Nicholas Farmer Jan 30 '23 at 00:06
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/251481/discussion-between-nicholas-farmer-and-lorem-ipsum). – Nicholas Farmer Jan 30 '23 at 00:14
  • Perhaps you could edit your question to show one of your attempts and the error you get. – Paulw11 Jan 30 '23 at 03:43
  • @NicholasFarmer I linked an answer where Peter Friese shows both a completion handler and the `async`/`await` approach that lormipsem suggests. If you're still having trouble making it work for your case after reading that, edit your question to show what you tried based on it. – Frank van Puffelen Jan 30 '23 at 04:20
  • I've added extra info as requested, I've also looked at the links, thanks. But I'm completely confused – Nicholas Farmer Jan 31 '23 at 20:52
  • Can this question please be re-opened, I've edited as requested with clear examples I'm trying but I NEED HELP!!!!! – Nicholas Farmer Jan 31 '23 at 21:50

1 Answers1

0

Below is an example of a working function using the completion-handler approach. I would use this approach for now before diving into async-await because it's much simpler. With async-await there are more things to consider in the larger architecture of the codebase (i.e. using actors, assigning a main actor, using detached or attached tasks, etc.).

func getUser(_ completion: @escaping (_ done: Bool) -> Void) {
    self.db.collection("userSetting").getDocuments() { (snapshot, err) in
        guard let snapshot = snapshot else {
            if let error = error {
                print(error)
            }
            completion(false) // always call the completion handler before leaving
            return // terminate this function call
        }
        for doc in snapshot.documents {
            
            // Unwrap the fields in the document safely. You don't want to crash
            // the entire app because you weren't able to get a string from a
            // document read.
            if let userTrust = doc.get("userTrust") as? String,
               let userGrade = doc.get("userGrade") as? String,
               let userDisclaimer = doc.get("userDisclaimer") as? String {
                let row = [userTrust, userGrade, userDisclaimer]
                self.userArray.append(row)
            }
        }
        
        // Once you've finished parsing the documents, call the completion.
        completion(true)
    }
}

getUser { done in
    if done {
        // do something
    } else {
        // do something else
    }
}

What I don't understand is why you get a bunch of documents from a collection but then do something like this in each document loop:

self.userTrustInfo = userTrust
self.userGradeInfo = userGrade

Did you mean to just grab the user's settings document? Or did you mean to query an entire collection of documents?

trndjc
  • 11,654
  • 3
  • 38
  • 51