Unable to Fetch Users Firebase Firestore - swift

Unable to fetch list of users from firebase. print returns as [] . Code is below. Any help would be awesome!
let COLLECTION_USERS = Firestore.firestore().collection("users")
func fetchUsers() {
COLLECTION_USERS.getDocuments { snapshot, _ in
guard let documents = snapshot?.documents else {return}
self.users = documents.compactMap({try? $0.data(as: User.self)})
print(self.users)
}
I expect the 7 users that I have signed up to print that data.

If documents.count shows that you've got 7 documents, but self.users contains no elements after running the mapping, this indicates your Firestore documents can't be mapped to your Swift structs.
Please make sure that the data types on your Swift structs match the types used in your Firestore documents.
You should also use code that is more error-resilient. In your code, you explicitly drop the error parameter on the closure - you rather don't want to do this.
The following code snippet (taken from the official docs) shows how to do this.
let docRef = db.collection("cities").document("BJ")
docRef.getDocument { (document, error) in
// Construct a Result type to encapsulate deserialization errors or
// successful deserialization. Note that if there is no error thrown
// the value may still be `nil`, indicating a successful deserialization
// of a value that does not exist.
//
// There are thus three cases to handle, which Swift lets us describe
// nicely with built-in Result types:
//
// Result
// /\
// Error Optional<City>
// /\
// Nil City
let result = Result {
try document?.data(as: City.self)
}
switch result {
case .success(let city):
if let city = city {
// A `City` value was successfully initialized from the DocumentSnapshot.
print("City: \(city)")
} else {
// A nil value was successfully initialized from the DocumentSnapshot,
// or the DocumentSnapshot was nil.
print("Document does not exist")
}
case .failure(let error):
// A `City` value could not be initialized from the DocumentSnapshot.
print("Error decoding city: \(error)")
}
}

Related

Array populated from Firebase returned empty, but data was retrieved. Why?

Still learning some swift and managed to advance and retrieving data from a firestore database. I have a Data Controller whose task is to offload all the data retrieving from firestore. It does the calls and gets data, but when returning the info from the first function I have implemented on it, it's empty.
Here's an example of the funcion:
func fetchUnidades(for foo: MyFirstEnum, and bar: MySecondEnum ) -> [MyClassType]{
let db = Firestore.firestore()
let colPath = "my/firebase/path"
let results = [MyClassType]()
let collection = db.collection(colPath)
collection.whereField("myField", isEqualTo: foo.rawValue).getDocuments() { querySnapshot, err in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
do {
print("\(document.documentID) => \(document.data())") // 1st print
let newMyClass = try document.data(as: MyClassType.self)
results.append(newMyClass)
print("here") // 2nd print - debug breakpoint
}catch (let error) {
print("\(error)")
}
}
}
}
print("DC - Recovered \(results.count) results")
return results
}
Assume MyFirstEnum, MySecondEnum and MyClassType are correct, because the database retrieves info. On the 1st print line, there's output for data retrieved, and on the 2nd print - debug breakpoint line, if I do a po results, it has one value, which is the one retrieved as you can see here:
unidades being the name on my code of results on this example.
But right after continuing with the execution, unidades, aka results is empty, the line:
print("DC - Recovered \(results.count) results")
prints DC - Recovered 0 results and the return also returns an empty array with zero values on it.
Any idea about why this might be happening? And how to solve the issue? Obviously the goal is to return the info...
That's because the result comes asynchronously. Your fetchUnidades returns results array before it's populated.
You need to add a completion closure in this case. Instead of returning results you call that completion closure and pass the results as its argument.
func fetchUnidades(for foo: MyFirstEnum, and bar: MySecondEnum, completion: (results: [MyClassType]?, error: Error?) -> Void) {
let db = Firestore.firestore()
let colPath = "my/firebase/path"
let collection = db.collection(colPath)
collection.whereField("myField", isEqualTo: foo.rawValue).getDocuments() { querySnapshot, err in
if let err = err {
print("Error getting documents: \(err)")
completion(nil, err)
} else {
let results = [MyClassType]()
for document in querySnapshot!.documents {
do {
print("\(document.documentID) => \(document.data())") // 1st print
let newMyClass = try document.data(as: MyClassType.self)
results.append(newMyClass)
print("here") // 2nd print - debug breakpoint
}catch (let error) {
print("\(error)")
}
}
completion(results, nil)
}
}

How can I add these Firestore fields to a Dictionary?

I am looking to add all my "usernames" into a dictionary. I am having some trouble doing this. I am sure it's very obvious, but I am very new to coding.
I am stuck at, right now and can't seem to find a clear answer anywhere:
func fetchUser() {
let db = Firestore.firestore()
let usernameSearch = db.collection("users")
usernameSearch.getDocuments { (snapshot, error) in
if error != nil {
print("Error obtaining usernames")
} else {
for field in snapshot!.documents {
let field = field.get("username")
print(field!)
}
}
}
}
I would really appreciate it if somebody could help me out. I am sure it's very obvious, or I'm just doing it totally wrong.
First, get into the habit of safely unwrapping over force unwrapping. And choose more accurate names for your objects (i.e. usersCollection over usernameSearch). However, in this case, there's no need to instantiate individual properties for the database and the collection since they're not being used anywhere else but here (so be efficient and omit them).
var usersDictionary = [String: [String]]()
func fetchUser() {
Firestore.firestore().collection("users").getDocuments { (snapshot, error) in
if let snapshot = snapshot { // unwrap the snapshot safely
var usernames = [String]()
for doc in snapshot.documents {
if let username = doc.get("username") as? String {
usernames.append(username)
}
}
usersDictionary["usernames"] = usernames
} else {
if let error = error {
print(error)
}
}
}
}
Or if you actually meant an array of users:
var usersArray = [String]()
func fetchUser() {
Firestore.firestore().collection("users").getDocuments { (snapshot, error) in
if let snapshot = snapshot { // don't force unwrap with !
for doc in snapshot.documents {
if let username = doc.get("username") as? String {
usersArray.append(username)
}
}
} else {
if let error = error {
print(error)
}
}
}
}
I'm assuming that what you're looking for is an Array, not a Dictionary. I'll also assume that you are indeed getting the correct value that you'd expect out of field.get("username"), e.g. a string such as "Bob." Therefore, what you are trying to do is map the list of document objects to a list of strings.
If you scroll to the Topics section of the Array documentation from Apple, you can find some of the operations they provide for arrays such as snapshot!.documents.
One of those operations is actually map, and its description is:
Returns an array containing the results of mapping the given closure over the sequence’s elements.
https://developer.apple.com/documentation/swift/array/3017522-map
In other words, you provide a transformation to perform for each instance of a document belonging to the snapshot!.documents Array and get back a new Array containing the resultant values of that transformation.
In this case I will use a more specific operation; compactMap. We have to try and cast the returned value from Any to String. If that does not succeed, it will return nil, and we'll want to filter that out. I expect it to be an unlikely case due to the type requirements made by the Firebase Console, but it's good to be aware of it. Here is the example:
func fetchUsernames(from usernameCollection: String, completion: #escaping ([String]) -> Void) {
let db = Firestore.firestore()
let collection = db.collection(usernameCollection)
collection.getDocuments { snapshot, error in
guard error != nil,
let usernames = snapshot?.documents.compactMap { $0.get("username") as? String }
else { return print("Error obtaining usernames") }
completion(usernames)
}
}
The key line here being let usernames = snapshot?.documents.compactMap { $0.get("username") }. We are passing the map function a closure. This closure is passed an argument itself; each value from the snapshot?.documents array. You may refer to this passed in value with $0.

Get Data from firebase for swift

I have below code to get data from my firebase database
db.collection("users").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
print("starting name display")
for document in (querySnapshot?.documents)! {
let documentUserId = document.get("uid") as?String
let temp = document.data()["displayName"]
print(temp)
}
}
}
The print statement displays as optional("test name")
Why am i keep getting optional in my string. Same displays on the screen as well.
You need to un-wrap because it's an Optional. Means it could have a value or it could not have a value. So this is one method to handle it:
let temp = document.data()["displayName"] ?? ""
print(temp)
You could also use if let or guard let statements if you need to handle the cases where the value is actually empty.
Note: Take a look at the basics of swift. There is a separate section for Optionals.

Swift Firestore custom objects from query while listening for realtime updates with snapshotListener

I've got issues converting querySnapshots that is observed for changes with snapshotListener to custom objects.
This is my code but the compiler is complaining:
func getCartData(db: Firestore){
db.collection("test")
.whereField("Cart", arrayContains: "testItem")
.addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("Error fetching document: \(error!)")
return
}
for doc in documents {
guard let data = doc.data() else { //here is an error saying: Initializer for conditional binding must have Optional type, not '[String : Any]'
print("Document data was empty.")
return
}
let result = Result {
try doc.data(as: CartDataDocument.self)
}
switch result {
case .success(let userDataDoc):
if let userDataDoc = userDataDoc {
// A `userDataDoc` value was successfully initialized from the DocumentSnapshot.
self.cartData = userDataDoc
} else {
// A nil value was successfully initialized from the DocumentSnapshot,
// or the DocumentSnapshot was nil.
print("Document does not exist")
}
case .failure(let error):
// A `userDataDoc` value could not be initialized from the DocumentSnapshot.
print("Error decoding UserDataDocument: \(error)")
}
}
}
}
I was able to make custom objects of one Firestore document that got listened to with a snapshotListener but had no luck with this query.
The error seems correct. If you look at the declaration that Xcode shows while autocompleting, doc.data() is indeed a non-nullable [String: Any].
If you want to check if a document is empty you can do it like this:
guard !doc.data().isEmpty else {
print("Document data was empty.")
return
}
Otherwise, you could also override init(from decoder: Decoder) throws in CartDataDocument and provide default values for all keys. This obviously goes in a a different direction: instead of ignoring empty documents, you are adding them to the list with default values. But I've got to ask: why do you have empty documents?

Extremely confused by the scope of Firestore GetDocuments request and the in loop of Swift

All that I'm trying to do is to check whether the value for a 'key' exists in a Firestore collection as a document and return a Bool.
But I can't seem to return anything within the getDocument,
so I thought that I should then keep a results var and update the results var but the changes I make to results don't stick and it stays default false.
How do I simplify this whole mess?
func checkIfValid(db: Firestore, key: String) -> Bool {
let resolve = db.collection("keys").document(key)
var results = false
resolve.getDocument{ (document, error) in
if let document = document, document.exists {
var results = true
} else { results = false }
}
print(results)
return results
}
Reading the firebase docs, they have a small warning below the sample code.
Note: If there is no document at the location referenced by docRef,
the resulting document will be empty and calling exists on it will
return false.
However, you need to add a completion handler to the function given you're working with network requests. Swift will return the result variable given you specified it; ignoring any response from the getDocuments handler.
I changed the function to fix your mess.
func checkIfValid(db: Firestore, key: String, completion: #escaping(Bool) -> ()) {
let docRef = db.collection("user").document(key)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
print("document exists.")
completion(true)
} else {
print("document does not exists.")
completion(false)
}
}
}
And to get the response, just use this.
checkIfValid(db: db, key: "", completion: {success in
print(success)
})