Swift - Fetch elements from Firestore - swift

I have a problem with this function:
func fetchGameFromDB(completionHandler: #escaping ([GamesObject]) -> Void) {
db.collection("games").getDocuments { (querySnapshot, err) in
if let err = err {
print("Error: \(err)")
} else {
self.gameObject = []
for document in querySnapshot!.documents {
print("document \(document.data())")
if let name = document.data()["name"] as? String {
let docRef = self.db.collection("games").document(name)
docRef.getDocument { document, error in
if let document = document {
let data = document.data()
let name = data?["name"] as? String ?? ""
let urlStanding = data?["urlStanding"] as? String ?? ""
let img = data?["gameImg"] as? String ?? ""
let urlUpcoming = data?["urlUpcoming"] as? String ?? ""
self.gameObject.append(GamesObject(name: name, gameImg: img, urlStanding: urlStanding, urlUpcoming: urlUpcoming))
// here i have elements in gameObject
}
// here i have elements in gameObject
}
// here gameObject = []
}
// here gameObject = []
}
completionHandler(self.gameObject)
// here gameObject = []
}
}
}
I get my data well and I add it to my array but when I get to the completionHandler the array is empty.

I find solution, i check if gameObject.count == querySnapshot?.count then I use my completionHandler
func fetchGameFromDB(completionHandler: #escaping ([GamesObject]) -> Void) {
db.collection("games").getDocuments { (querySnapshot, err) in
if let err = err {
print("Error: \(err)")
} else {
self.gameObject = []
querySnapshot?.documents.forEach({ (document) in
if let name = document.data()["name"] as? String {
let docRef = self.db.collection("games").document(name)
docRef.getDocument { document, error in
if let document = document {
let data = document.data()
let name = data?["name"] as? String ?? ""
let urlStanding = data?["urlStanding"] as? String ?? ""
let img = data?["gameImg"] as? String ?? ""
let urlUpcoming = data?["urlUpcoming"] as? String ?? ""
self.gameObject.append(GamesObject(name: name, gameImg: img, urlStanding: urlStanding, urlUpcoming: urlUpcoming))
if self.gameObject.count == querySnapshot?.count {
completionHandler(self.gameObject)
}
}
}
}
})
}
}
}

the first answer there is no problem as long as the missing documents do not exist. but, that cannot be escaped if any of the documents are missing.
how about use to 'DispatchGroup' ?
func fetchGameFromDB(completionHandler: #escaping([GamesObject]) -> Void) {
db.collection("games").getDocuments { (querySnapshot, error) in
guard let docs = querySnapshot?.documents, !docs.isEmpty else {
if let error = error {
print(error)
}
return
}
let group = DispatchGroup()
docs.forEach { doc in
group.enter()
guard let name = doc.data()["name"] as? String else {
group.leave()
return
}
let docRef = self.db.collection("games").document(name)
docRef.getDocument { document, error in
if let document = document, let data = document.data() {
//do something...
gameObjects.append(GamesObject(...)) //init object
}
group.leave()
}
}
group.notify(queue: .main) {
completionHandler(gameObjects)
}
}
}

Related

Get data from firestore and assign it to an array of dictionaries

I am trying to get data from firestore collection and assign it to an array of dictionaries. for this part of the code below... i get the error "Cast from 'QuerySnapshot?' to unrelated type '[[String : Any]]' always fails" and the console prints "is not working".
guard let snap = snapshot as? [[String:Any]] else {
print("is not working")
completion(.failure(DatabaseError.failedToFetch))
return
}
Here is the full code.
// fetches and returns all conversations for the user with passed in uid
public func getAllConversations(for uid: String, completion: #escaping(Result<[Conversation], Error>) -> Void) {
print("fetching all convos")
//NEW
let db = Firestore.firestore()
let CurrentUser = Auth.auth().currentUser?.uid
let ListRef = db.collection("users").document(CurrentUser!).collection("conversations")
// fetch the current users convo list
ListRef.getDocuments { snapshot, error in
if let err = error {
debugPrint("Error fetching documents: \(err)")
} else {
guard let snap = snapshot as? [[String:Any]] else {
print("is not working")
completion(.failure(DatabaseError.failedToFetch))
return
}
print("is working")
let conversations: [Conversation] = snap.compactMap({ dictionary in
guard let id = dictionary["id"] as? String,
let name = dictionary["name"] as? String,
let otherUserUID = dictionary["other_user-uid"] as? String,
let latestMessage = dictionary["latest-message"] as? [String:Any],
let date = latestMessage["date"] as? String,
let message = latestMessage["message"] as? String,
let isRead = latestMessage["is-read"] as? Bool else {
return nil
}
//save other user ID to a global var
self.test = otherUserUID
//assign data into an array of dictionaries
let latestConvoObject = LatestMessage(date: date, text: message, isRead: isRead)
return Conversation(id: id, name: name, otherUserUid: otherUserUID, latestMessage: latestConvoObject)
})
completion(.success(conversations))
}
}
}
There are a numbers of way to read that data, and the process can be simplified by conforming objects to the codable protocol but let me provide a straight forward example. I don't know what your Conversation object looks like so here's mine
class ConversationClass {
var from = ""
var to = ""
var msg = ""
var timestamp = 0
convenience init(withDoc: DocumentSnapshot) {
self.init()
self.from = withDoc.get("from") as? String ?? "no from"
self.to = withDoc.get("to") as? String ?? "no to"
self.msg = withDoc.get("msg") as? String ?? "no msg"
self.timestamp = withDoc.get("timestamp") as? Int ?? 0
}
}
and then here's the the code that reads in all the conversation documents from a Collection, stores each in a ConversationClass object, puts those in an array and returns it through an escaping completion handler
func getConversations(completion: #escaping( [ConversationClass] ) -> Void) {
let conversationCollection = self.db.collection("conversations")
conversationCollection.getDocuments(completion: { snapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
guard let docs = snapshot?.documents else { return }
var convoArray = [ConversationClass]()
for doc in docs {
let convo = ConversationClass(withDoc: doc)
convoArray.append(convo)
}
completion(convoArray)
})
}

Fetched data from Firestore returns duplicates

I am trying to fetch data from firebase firestore. The problem i have is that my fetch is returning the results x4 times. For example when i do print(name) it print the users name x4 times.
I think there may be a loop that is not working correctly?
// fetches and returns all conversations for the user with passed in uid
public func getAllConversations(for uid: String, completion: #escaping(Result<[Conversation], Error>) -> Void) {
let CurrentUser = Auth.auth().currentUser?.uid
let db = Firestore.firestore()
let ConversationRef = db.collection("users").document(CurrentUser!).collection("conversations").document(
"jVymlfbpuAYQQ9Brf8SbUZ7KCGg1")
// get the otherUserUId TO DO
ConversationRef.getDocument { snapshot, error in
if error != nil {
print("Error connecting to database")
} else {
if let document = snapshot {
if document.exists {
let data = document.data()
print(data)
let conversations: [Conversation] = data!.compactMap ({ dictionary in
guard let conversationId = data!["id"] as? String,
let name = data!["name"] as? String,
let otherUserUid = data!["other_user-uid"] as? String,
let latestMessage = data!["latest-message"] as? [String:Any],
let date = latestMessage["date"] as? String,
let message = latestMessage["message"] as? String,
let isRead = latestMessage["is-read"] as? Bool
else {
return nil
}
print(name)
let latestMessageObject = LatestMessage(date: date, text: message, isRead: isRead)
return Conversation(id: conversationId, name: name, otherUserUid: otherUserUid, latestMessage: latestMessageObject)
})
completion(.success(conversations))
}
else {
completion(.failure(DatabaseError.failedToFetch))
return
}
}
}
}
}
Please note that ConversationRef.getDocument{..} will only Return One Specific Document, which you’re Referring here :
let ConversationRef = db.collection("users").document(CurrentUser!).collection("conversations").document("jVymlfbpuAYQQ9Brf8SbUZ7KCGg1”)
So the let data = document.data()
will be single [String:Any] object(in this case Single ‘Conversation’),
not the Array of Dictionaries(eg: [Conversations]).
Try doing it this way:
// fetches and returns all conversations for the user with passed in uid
public func getAllConversations(for uid: String, completion: #escaping(Result<Conversation, Error>) -> Void) {
let CurrentUser = Auth.auth().currentUser?.uid
let db = Firestore.firestore()
let ConversationRef = db.collection("users").document(CurrentUser!).collection("conversations").document(
"jVymlfbpuAYQQ9Brf8SbUZ7KCGg1")
// get the otherUserUId TO DO
ConversationRef.getDocument { snapshot, error in
if error != nil {
print("Error connecting to database")
} else {
if let document = snapshot {
if document.exists {
if let data = document.data() {
if let conversationId = data["id"] as? String,
let name = data["name"] as? String,
let otherUserUid = data["other_user-uid"] as? String,
let latestMessage = data["latest-message"] as? [String:Any],
let date = latestMessage["date"] as? String,
let message = latestMessage["message"] as? String,
let isRead = latestMessage["is-read"] as? Bool {
print(name)
let latestMessageObject = LatestMessage(date: date, text: message, isRead: isRead)
let conversations = Conversation(id: conversationId, name: name, otherUserUid: otherUserUid, latestMessage: latestMessageObject)
completion(.success(conversations))
}
}
}
else {
completion(.failure(DatabaseError.failedToFetch))
return
}
}
}
}
}
// fetches and returns all conversations for the user with passed in uid
public func getAllConversations(for uid: String, completion: #escaping(Result<[Conversation], Error>) -> Void) {
let CurrentUser = Auth.auth().currentUser?.uid
let db = Firestore.firestore()
let ConversationRef = db.collection("users").document(CurrentUser!).collection("conversations")
ConversationRef.addSnapshotListener { snapshot, error in
if error != nil {
print("Error connecting to database")
} else {
guard let snap = snapshot else {
completion(.failure(DatabaseError.failedToFetch))
return
}
for document in snap.documents {
let data = document.data()
print(data)
guard let conversationId = data["id"] as? String,
let name = data["name"] as? String,
let otherUserUid = data["other_user-uid"] as? String,
let latestMessage = data["latest-message"] as? [String:Any],
let date = latestMessage["date"] as? String,
let message = latestMessage["message"] as? String,
let isRead = latestMessage["is-read"] as? Bool else {
return
}
print(name)
let latestMessageObject = LatestMessage(date: date, text: message, isRead: isRead)
let conversations = [Conversation(id: conversationId, name: name, otherUserUid: otherUserUid, latestMessage: latestMessageObject)]
completion(.success(conversations))
}
}
}
}

What is wrong in this 2 query's please

This is a query I want to do in Swift with Firestore Database.
I spend a lot of time to make this code work. In debugger when it arrived in the first db.collection line the debugger jump to the second db.collection line without process the code between. After processing the 2. db.collection line he go back to the first and process the code.
func readAirplanes() {
var airplaneArray = [String]()
var arrayPosition = 1
db.collection("airplane").whereField("Userid", isEqualTo: userID).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
return
} else {
self.lbNext.isHidden = false
self.btEdit.isUserInteractionEnabled = true
self.btDelete.isUserInteractionEnabled = true
for document in querySnapshot!.documents {
airplaneArray.append(document.documentID)
}
}
}
db.collection("airplane").document(airplaneArray[arrayPosition]).getDocument() { (document, error) in
if let document = document, document.exists {
self.tfPrefix.text = document.get("Prefix") as? String
self.tfIcao.text = document.get("Icao") as? String
self.tfModel.text = document.get("Model") as? String
self.tfSerial.text = document.get("Serial") as? String
self.tfManifacture.text = document.get("Manifacture") as? String
self.tfYear.text = document.get("Year") as? String
self.tfRules.text = document.get("Rules") as? String
self.tfOperator.text = document.get("Operator") as? String
self.tfCVA.text = document.get("CVA") as? String
} else {
print("Document does not exist")
}
}
}
any help please
Firestore`s queries run asyncronously, not one after another. So the second query may start earlier than the first is completed.
If you want to run them one by one you need to put 2nd query into 1st.
Try this:
func readAirplanes() {
var airplaneArray = [String]()
var arrayPosition = 1
db.collection("airplane").whereField("Userid", isEqualTo: userID).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
return
} else {
self.lbNext.isHidden = false
self.btEdit.isUserInteractionEnabled = true
self.btDelete.isUserInteractionEnabled = true
for document in querySnapshot!.documents {
airplaneArray.append(document.documentID)
}
db.collection("airplane").document(airplaneArray[arrayPosition]).getDocument() { (document, error) in
if let document = document, document.exists {
self.tfPrefix.text = document.get("Prefix") as? String
self.tfIcao.text = document.get("Icao") as? String
self.tfModel.text = document.get("Model") as? String
self.tfSerial.text = document.get("Serial") as? String
self.tfManifacture.text = document.get("Manifacture") as? String
self.tfYear.text = document.get("Year") as? String
self.tfRules.text = document.get("Rules") as? String
self.tfOperator.text = document.get("Operator") as? String
self.tfCVA.text = document.get("CVA") as? String
} else {
print("Document does not exist")
}
}
}
}
}

How to populate loaded records from firebase?

I wrote the function to lad the records from firebase but there's an error
Escaping closure captures mutating 'self' parameter
The function is written as follows:
let db = Firestore.firestore()
#State var libraryImages: [LibraryImage] = []
mutating func loadImages() {
libraryImages = []
db.collection(K.FStore.CollectionImages.collectionName).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let snapshotDocuments = querySnapshot?.documents {
for document in snapshotDocuments {
let documentData = document.data()
let title: String = documentData[K.FStore.CollectionImages.title] as! String
let thumbnailUrl: String = documentData[K.FStore.CollectionImages.thumbnailUrl] as! String
let svgUrl: String = documentData[K.FStore.CollectionImages.svgUrl] as! String
let libraryImageItem = LibraryImage(title: title, thumbnailUrl: thumbnailUrl, svgUrl: svgUrl)
self.libraryImages.append(libraryImageItem)
}
}
}
}
}
Does anyone know what is causing an error and how to get rid of it?
Move all this into reference type view model and use it as observed object in your view
Here is a demo of possible approach:
struct DemoView: View {
#ObservedObject var vm = ImagesViewModel()
// #StateObject var vm = ImagesViewModel() // << SwiftUI 2.0
var body: some View {
Text("Loaded images: \(vm.libraryImages.count)")
.onAppear {
self.vm.loadImages()
}
}
}
class ImagesViewModel: ObservableObject {
let db = Firestore.firestore()
#Published var libraryImages: [LibraryImage] = []
func loadImages() {
libraryImages = []
db.collection(K.FStore.CollectionImages.collectionName).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let snapshotDocuments = querySnapshot?.documents {
var images = [LibraryImage]()
for document in snapshotDocuments {
let documentData = document.data()
let title: String = documentData[K.FStore.CollectionImages.title] as! String
let thumbnailUrl: String = documentData[K.FStore.CollectionImages.thumbnailUrl] as! String
let svgUrl: String = documentData[K.FStore.CollectionImages.svgUrl] as! String
let libraryImageItem = LibraryImage(title: title, thumbnailUrl: thumbnailUrl, svgUrl: svgUrl)
images.append(libraryImageItem)
}
DispatchQueue.main.async {
self.libraryImages = images
}
}
}
}
}
}

Setting up Nested Structs and populating via FireStore [Swift]

I have two structs, one nested within the other:
struct User {
let uid: String
let name: String
var pack: [Doggo]
}
struct Doggo {
let dogUid: String
let dogName: String
let dogBreed: String
let dogBorn: String
let dogProfileImageURL: String
}
Is the following code the proper way to access the firebase data for each?
guard let userUid = Auth.auth().currentUser?.uid else { return }
let userRef = self.db.collection("users").document(userUid)
let packRef = self.db.collection("users").document(userUid).collection("pack")
userRef.getDocument { (document, error) in
if let document = document, document.exists {
let data = document.data()
let name = data?["name"] as? String ?? "Anonymous"
packRef.getDocuments(completion: { (snapshot, error) in
if let err = error {
debugPrint("Error fetchings docs: \(err)")
} else {
guard let snap = snapshot else { return }
for document in snap.documents {
let data = document.data()
let dogUid = data["dogUid"] as? String ?? "No dogUid"
let dogName = data["dogName"] as? String ?? "No dogName"
let dogBreed = data["dogBreed"] as? String ?? "No dogBreed"
let dogBorn = data["dogBorn"] as? String ?? "No dogBorn"
let dogProfileImageURL = data["dogProfileImageURL"] as? String ?? "No dogProfileImageURL"
let newDoggo = Doggo(dogUid: dogUid, dogName: dogName, dogBreed: dogBreed, dogBorn: dogBorn, dogProfileImageURL: dogProfileImageURL)
self.doggos.append(newDoggo)
print(newDoggo)
}
}
self.user = User(uid: userUid, name: name, pack: self.doggos)
print(self.user)
self.configureHomeController()
self.configureMenuController()
})
} else {
print("Document does not exist")
}
}
It appears to work the way I'd like it to, but I don't want to run into issues down the line as it's quite foundational to the rest of the app.