Why I Couldn't Assign Fetched Values from Firestore to an Array in Swift

How to retrieve data held in an array from Firestore using Swift

Rather than storing each property in a separate array, you may want to consider representing it with a struct. Something like:

struct Item {
var shoutText: String?
var postedBy: String?
var date: String?
var pollQuestions : [String]
}

Then, on your view controller, declare a property:

var items: [Item] = []

Then, in your snapshot listener, you can populate that array:

func getData() {
Firestore.firestore().collection("Posts").whereField("postedTo", arrayContains: userId).order(by: "date", descending: true).addSnapshotListener { (snapshot, error) in
if error != nil {
print(error?.localizedDescription)
} else {
if let snapshot = snapshot, !snapshot.isEmpty {
print("Posted data got")

self.items = snapshot.documents.map { document in
Item(shoutText: document.get("shout") as? String,
postedBy: document.get("postedBy") as? String,
date: document.get("date") as? String,
pollQuestions: document.get("pollQuestions") as? [String] ?? [])
}

self.receivedCollectionView.reloadData()

} else {
print("GET POSTED MESSAGES no data")
}

}
}
}

Later, you can access this data:

self.items[itemIndex].pollQuestions[pollQuestionIndex]

getting data out of a closure that retrieves data from firebase

That's because when you fetch data from Firebase the call is Asynchronous. What you can do:

Option 1 - Set your logic inside the closure (Like what you have that print the var inside the closure).

Option 2 - Define your own closure that going to receive your data like:

func myMethod(success:([String])->Void){

ref?.observeEventType(.Value, withBlock: { snapshot in
var newNames: [String] = []
for item in snapshot.children {
if let item = item as? FIRDataSnapshot {
let postDict = item.value as! [String: String]
newNames.append(postDict["name"]!)
}
}
success(newNames)
})
}

Option 3 - Use the delegate pattern

protocol MyDelegate{
func didFetchData(data:[String])
}

class MyController : UIViewController, MyDelegate{

func myMethod(success:([String])->Void){
ref?.observeEventType(.Value, withBlock: { snapshot in
var newNames: [String] = []
for item in snapshot.children {
if let item = item as? FIRDataSnapshot {
let postDict = item.value as! [String: String]
newNames.append(postDict["name"]!)
}
}
self.didFetchData(newNames)
})
}

func didFetchData(data:[String]){
//Do what you want
}

}

Get data from Firestore and properly append to array

let shelter = Shelter(id: Int(value.id), title: value.title, image: value.image, availableSpaces: value.available, distance: value.distance, gender: value.gender)

Here value is of type [String:Any]. So you cant do value.title . You need to do value["title"] as? String ?? "" and Similarly for id,image,distance,etc.

So the final code becomes:

let shelter = Shelter(id: Int(value["id"], title: value["title"], image: value["image"], availableSpaces: value["available"], distance: value["distance"], gender: value["gender"])

Downcast it accordingly.

UPDATE

replace your code with this

if let shelter = Shelter(id: value["id"] as? Int ?? -1, title: value["title"] as? String ?? "", image: value["image"] as? String ?? "", available: value["available"] as? Int ?? -1, distance: value["distance"] as? Double ?? -1, gender: value["gender"] as? String ?? "") { 
self.shelters.append(shelter)
} else {
print("provided data is wrong.")
}

Firestore data is not displayed in listview. Identifiable not set up correctly?

Well after a good night of sleep I was cured of my temporary blindness and found that I was missing the @ObservedObject property in my view.

struct FIRBauthView: View {
@ObservedObject var firebaseCtrl: firebaseController = firebaseController()

instead of

struct FIRBauthView: View {
let firebaseCtrl: firebaseController = firebaseController()

Now the data is showing up as intended.

Firebase getDocument (querySnapshot) is not working

I think you may want to investigate a completion handler. The idea is to call an asynchronous function and then perform a task when that function has completed. The below code can almost be directly applied as a solution to your question.

So here's an example you can start with. Suppose we have an app with users stored in a users collection

users
uid_0
name: "Steve"
uid_1
name: "Danno"
uid_2
name: "Junior"
uid_3
name: "Tani"

and a new user wants to sign up, but the app doesn't allow for duplicate names. So we need to query our users collection to determine if the name is already in use. Here's the code that calls the query function.

self.doesUserNameExist(userName: "Steve" ) { doesExist in
if doesExist == true {
print("yep, it exists")
} else {
print("Nope - it's not there")
}
}

print("this code will execute before the above code")

That will print yep, it exists

and here's the actual function that performs the query. Note how we're using an completion handler thats @escaping the closure when it's done.

//determine if a user name exists in Firestore, return true if it does, false if not
func doesUserNameExist(userName: String, completion: @escaping( (Bool) -> Void) ) {
let usersCollection = self.db.collection("users")
usersCollection.whereField("name", isEqualTo: userName).getDocuments(completion: { querySnapshot, error in
if let err = error {
print(err.localizedDescription)
return
}

guard let docs = querySnapshot?.documents else { return }

if docs.count > 0 {
completion(true)
} else {
completion(false)
}
})
}

This is the typical way to 'return' data from an asynchronous function as it allows the function to execute, the data to be valid and then pass that data back to the calling function in an asynchronous way.

Note that any code following the calling function, like this

print("this code will execute before the above code")

Will execute before the code in the function closure.

EDIT

This above called is called like this

self.doesUserNameExist(userName: "Jay", completion: { doesItExist in
print(doesItExist)
})


Related Topics



Leave a reply



Submit