Storing Asynchronous Cloud Firestore Query Results in Swift

Storing asynchronous Cloud Firestore query results in Swift

You need a dispatch group in addition to a completion

func convertToNames(arr: [String],completion:@escaping(([String]) -> ())) {

var newArr : [String] = []
let g = DispatchGroup()
for id in arr {
let docRef = db.collection("users").document(id)
g.enter()
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
let data = document.get("firstname") ?? "nil"

print("gotten data: \(data)")
newArr.append(String(describing: data))
g.leave()
} else {
print("Document does not exist")
g.leave()
}
}
}

g.notify(queue:.main) {
print("NEW ARRAY: \(newArr)")
completion(newArr)
}
}

Call

convertToNames(arr:<#arr#>) { res in
print(res)
}

Handling asynchronous Firestore data reading in tableView - iOS

As @Galo Torres Sevilla mentioned, addSnapshotListener method is async and you need to add completion handler to your getDataFromDatabase() function.

Make following changes in your code:

  1. Declare Global variable for notes.

    var list_notes = [String]()
  2. Add completion handler to getDataFromDatabase() method.

    func getDataFromDatabase(callback: @escaping([String]) -> Void) {
    var notes: [String] = []
    collectionRef = Firestore.firestore().collection("Notes")

    collectionRef.addSnapshotListener { querySnapshot, error in
    guard let documents = querySnapshot?.documents else {
    print("Error fetching documents: \(error!)")
    return
    }
    notes = documents.map { $0["text"]! } as! [String] // text is a field saved in document
    print("inside notes: \(notes)")

    callback(notes)
    }
    }
  3. Lastly, call function on appropriate location where you want to fetch notes and assign retrieved notes to your global variable and reload TableView like below:

    self.getDataFromDatabase { (list_notes) in
    self.list_notes = list_notes
    DispatchQueue.main.async {
    self.tableView.reloadData()
    }
    }

Changes in TableView:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("tableview numberOfRowsInSection called")
return self.list_notes.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("Cells")
let cell = tableView.dequeueReusableCell(withIdentifier: "firstCell", for: indexPath)

cell.textLabel!.text = String(self.list_notes[indexPath.row].prefix(30))

return cell
}

addSnapShotListener triggers all the function dependent to it?

When the data changed (and at the initial load) only the code inside your listener (the ... in your question) is called. There is no effect on functions A or B. So any code that needs the data from the database, has to be inside the snapshot callback, be called from there, or be otherwise synchronized with that code.

If this is surprising to you, you may be new to dealing with asynchronous callbacks. If that's the case, I recommend checking out:

  • How do I save data from cloud firestore to a variable in swift?
  • Storing asynchronous Cloud Firestore query results in Swift
  • SwiftUI - Wait until Firestore getDocuments() is finished before moving on
  • How can I change the order of functions triggered?

How to properly set State with Firestore getDocuments

The only thing that's wrong is your expectations about how getDocuments works.

getDocuments is asynchronous, and the callback (completion) you pass to it is executed after it returns. Print something in the callback and it'll be more clear what's actually going on.

Given that, checkDoc cannot possibly return the correct value synchronously unless you do something to make it block (which you should not do). You will need to handle all query results asynchronously.

How can I change the order of functions triggered?

Data is loaded from Firebase asynchronously. Since that may take some time, your completion handler is called later than you might expect.

For this reason, any code that needs the data from the database, needs to be inside the completion handler, or be called from there.

So the simplest fix is to move the performSegue into the callback:

extension HomeViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

db.collection(K.FStore.newGameCpllection)
.whereField(K.FStore.uID, isEqualTo:players[indexPath.row].uID)
.addSnapshotListener { (querySnapshot, err) in
if let err = err {
print("Error getting game db: \(err)")
} else {
for doc in querySnapshot!.documents {

print("document saved!!")
self.gameDocumentID = doc.documentID
self.db.collection(K.FStore.newGameCpllection).document(self.gameDocumentID).updateData([
K.FStore.player2Field: self.playerInfo[K.FStore.nameField]!
]) { err in
if let err = err {
print("Error updating document: \(err)")
} else {
print("Document successfully updated")
}
print("segue triggered!!!")
self.performSegue(withIdentifier: K.homeToGameScreen, sender: self)
}
}

}
}
}
}

Also see:

  • Is Firebase getDocument run line by line?
  • Handling asynchronous Firestore data reading in tableView - iOS
  • Storing asynchronous Cloud Firestore query results in Swift, using a dispatch group to seemingly change the order of execution.
  • How do I save data from cloud firestore to a variable in swift?


Related Topics



Leave a reply



Submit