Firebase datadescription returns empty array
The main issue here is that FireStore is asynchronous. It takes time for Firestore to return data from the internet and that data is only valid inside the closure following the getDocument.
That means the print(Groups) function will execute before the code inside the closure, so it's empty. Moving the print inside the closure will work.
var Groups = [String:Any]()
let docRef = AppDelegate.db.collection("JoinedGroups").document(user)
docRef.getDocument(source: .cache) { (document, error) in
if let document = document {
let dataDescription = document.data()
self.Groups = dataDescription! // unwrapping here
print("Cached document data: \(dataDescription)")
} else {
print("Document does not exist in cache")
}
print(self.Groups)
}
}
May I also suggest you stick with naming conventions where vars are lowercases, e.g. groups instead of Groups. Upper case are usually for class definitions UserClass or GroupClass.
One last thing... documentID's (their 'key') cannot be changed. What that means is if your structure is this
JoinedGroups
jimmy@email.com
name: "Jimmy"
fav_food: "Pizza"
and you reference that user throughout your app, when they decide to change their email provider, you'll have to go through everywhere in your strucutre, read in that node, delete the node and write it back out with the updated email. How you fix it is it disassociate the documentID (key) from the data it contains
JoinedGroups
users_uid
name: "Jimmy"
fav_food: "Pizza"
as uid's never change, the email can change 20 times and not affect your app.
Cannot get documents from Firestore in Swift
Thanks to Jay's comment, I managed to fix the problem by having clientEmail
as a global variable and using a completion handler to assign the value in this function.
func getClientEmail() {
// Get email of client for corresponding contractor
db.collection("Clients").getDocuments(completion: { (querySnapshot, err) in
if let err = err {
print(err.localizedDescription)
return
} else {
for document in querySnapshot!.documents {
let result = Result {
try document.data(as: User.self)
}
switch result {
case .success(let client):
if let client = client {
if client.placementIDs[0] == self.user?.placementIDs[0] {
self.clientEmail = client.email
}
} else {
print("Document doesn't exist")
}
case .failure(let error):
print("Error decoding user: \(error)")
}
}
}
})
}
SearchBar problem while trying to search Firestore and reload the tableview
The issue in your question is that Firestore is asynchronous.
It takes time for Firestore to return documents you've requested and that data will only be valid within the closure calling the function. The code outside the closure will execute way before the data is available within the closure.
So here's what's going on.
func searchIngredients(text: String) -> Array<Any>{
let db = Firestore.firestore()
db.collection("Ingredients").whereField("compName", arrayContains: text).getDocuments{ (querySnapshot, err) in
//the data has returned from firebase and is valid
}
//the code below here will execute *before* the code in the above closure
self.tableView.reloadData()
ingredientsArray = searchedIngredientsArray
return ingredientsArray
}
what's happening is the tableView is being refreshed before there's any data in the array.
You're also returning the ingredientsArray before it's populated. More importantly, attempting to return a value from an asynchronous function can (and should) generally be avoided.
The fix is to handle the data within the closure
class ViewController: NSViewController {
var ingredientArray = [String]()
func searchIngredients(text: String) {
let db = Firestore.firestore()
db.collection("Ingredients").whereField("compName", arrayContains: text).getDocuments{ (querySnapshot, err) in
//the data has returned from firebase and is valid
//populate the class var array with data from firebase
// self.ingredientArray.append(some string)
//refresh the tableview
}
}
Note that the searchIngredients function should not return a value - nor does it need to
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)
}
Related Topics
How to Get Exactly the Same Point on Different Screen Sizes
Get Element from Array of Dictionaries According to Key
My Button Is Centered for iPhone 6 and 6 Plus, But Not for iPhone 5
Store Data in Custom Class Array in Core Data
Code Only Retrieving One Value from Data in Firebase
Could Not Cast Value of Type Uiview to [Customview] Using Xib
How to Unpack Multiple Levels of Nested JSON in Firebase Database
Avaudioplayer.Play() Works But Avaudioplayernode.Play() Fails
iOS 13 Swiftui: App Crashes Upon Launch on Real Device
Generic Class with Class-Bound Constraint Cannot Be Parametrized by Class-Bound Protocol
Collection View Layout Bug When Selectitem (Swift 5)
Swift: Filter a Dictionary with Array as Value
Value of Type 'Authdataresult' Has No Member 'Uid'
Don't Delete Some Rows from Uitableview
Swift - How to Set a Singleton to Nil
Custom Cell with Uitableview Inside Uicollectionviewcell
Using @Viewbuilder to Create Views Which Support Multiple Children