Retrieving all documents from collection in firebase - swift

Database Structure
futsal_list.document(futsal_uid).collection("book_info").document(date in format "May 9, 2019".collection("newrequest").document(userUid)
and in useruid there is map like
time[
6AM : timestamp
7AM : timstamp,
]
I am using google cloud firestore and i want to retrieve all the new request from all the dates and it need to be realtime listener. I also want to divide the table view section according to the date. And i am only showing the today and upcoming dates and no past dates.
problem -
when a new request arrived the table add the single request multiple times in the table and when i go back and come to the view then the data shown are fine.
this is what i have tried
func loadAllRequestFromFirebasee() {
db.collection("futsal_list").document(currentUser!.uid).collection("book_info").addSnapshotListener { (querySnapshot, error) in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
self.newRequestArray.removeAll()
self.sectionHeader.removeAll()
var validDates = [String] ()
for document in snapshot.documents {
let bookedDate = document.documentID
if !self.isPastDate(date: bookedDate) {
validDates.append(bookedDate)
}
}
for (bookedDateIndex, bookedDate) in validDates.enumerated() {
self.db.collection("futsal_list").document(self.currentUser!.uid).collection("book_info").document(bookedDate).collection("newrequest").getDocuments(completion: { (uidSnapshots, error) in
guard let uidSnapshot = uidSnapshots else {
print("Error fetching snapshots: \(error!)")
return
}
let userUids = uidSnapshot.documents
if userUids.count > 0 {
self.newRequestArray.append([])
self.sectionHeader.append(bookedDate)
for document in userUids {
print("\(document.documentID) => \(document.data())")
let dataDescription = document.data()
let userUid = document.documentID
let bookedTimes = dataDescription["time"] as! [String : Any]
self.db.collection("users_list").document(userUid).getDocument(completion: { (document, error) in
if let document = document, document.exists {
let data = document.data()
let userName = data!["user_full_name"] as? String ?? ""
// let futsalAddress = data!["futsal_address"] as? String ?? ""
let userPhone = data!["user_phone_number"] as? String ?? ""
let userProfilePic = data!["user_profile_image"] as? String ?? ""
for time in bookedTimes.keys {
let newRequest = NewRequest(userUid: userUid, userName: userName, bookDate: bookedDate, bookTime: time, userPhoneNumber: userPhone, userProfilePicture: userProfilePic)
self.newRequestArray[bookedDateIndex].append(newRequest)
self.newBookRequestTableView.reloadData()
}
}
})
}
}
})
}
}
}

Related

Swift - Fetch elements from Firestore

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)
}
}
}

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)
})
}

How to get an Dictionary from Firebase Firestore in Swift

I wonder how to get an Dictionary from my Firestore. For normal Arrays I've done it like that:
func returnArray(){
let newpath = Firestore.firestore().collection(path)
newpath.addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
return
}
self.Array = documents.map { (queryDocumentSnapshot) -> String in
let data = queryDocumentSnapshot.data()
let Name = data["myField"] as? String ?? ""
return Name
}
}
}
That works perfectly fine. My question is now how I have to change my code that its getting an Array out of the Firebase. I had an idea, but it doesn't work:
func returnDictionary(){
let newpath = Firestore.firestore().collection(path)
newpath.addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
return
}
self.Dictionary = documents.map { (queryDocumentSnapshot) -> // I don't know what to fill here
in
let data = queryDocumentSnapshot.data()
let Name = data["AnzeigeName"] as? String ?? ""
let ID = data["selfID"] as? String ?? ""
return ID: Name
}
}
}
What can I try next?
.map is going to give you an array, but you can use Dictionary's init(uniqueKeysWithValues:) to turn this into a Dictionary:
let dictionary : Dictionary<String,String> = .init(uniqueKeysWithValues: documents.compactMap { queryDocumentSnapshot -> (String,String)? in
let data = queryDocumentSnapshot.data()
if let name = data["AnzeigeName"] as? String, let id = data["selfID"] as? String {
return (id, name)
}
return nil
})
It's important to know that uniqueKeysWithValues will crash if the keys are not in fact unique, so you'd want to check for that first (look at #New Dev's comment about init(_:uniquingKeysWith:) to handle this). I'm also using compactMap to get rid of nil values.
(Note: In Swift, generally variable names are lowercased and type names are uppercased. Going against that pattern can make your code challenging to read for others)
Firestore Example
{
name: "Anonymous"
pet {
name: "Max"
age: "3"
}
}
Query
Firebase.firestore().collection(path).getDocuments { querySnapshot, error in
if let error = error {
print(error.localizedDescription)
} else {
for document in querySnapshot!.documents {
let data = document.data()
let name = data["name"]
let pet = data["pet"] as! Dictionary<String, String>
print("Name: \(name)")
print("Pet-name: \(pet["name"]!)")
print("Pet-age: \(pet["age"]!)")
}

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")
}
}
}
}
}

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.