There is a structure that contains structures in the form of an array. In order to fill the core, I must first fill in the internal ones and assign them to the main one. For this, I used the Dispatch Group () and the notices to add and write to the main structure with which I will work. Below is the code I'm using.
But as a result of this method I got a problem. Notification is performed earlier than necessary. What did I do wrong here?
Here is the output from the console:
DONE
[]
FBRecipe(name: "Eel kebab", count: "2", complexity: "3.75", time: "2", category: "Завтрак", type: "САЛАТЫ", about: "Lsvdvskld v\t", ingredient: [], cook: [], photo: [], idOwner: "XT2pgRnAZ8Q5pHH3dHsz5jYUZ613", shared: "0", planing: "0", timestamp: "1536761784.24662")
ingredinet
ingredinet
ingredinet
...
let loadRecipesGroup = DispatchGroup()
let loadItemsQueue = DispatchQueue(label: "ru.bryzgalov.cookbook.loadrecipes", qos: .userInteractive, attributes: [], autoreleaseFrequency: .workItem)
...
func loadRecipeList() {
var recipe = [FBRecipe]()
db.collection("RECIPES").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for documentRecipe in querySnapshot!.documents {
self.loadItemsQueue.async {
var ingredinet = [FBIngredient]()
var stage = [FBStage]()
var photo = [FBDishPhoto]()
db.collection("RECIPES/\(documentRecipe.documentID)/INGREDIENT").getDocuments(completion: { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for documentIngredient in querySnapshot!.documents {
self.loadItemsQueue.async(group: self.loadRecipesGroup) {
let newIngredinet = FBIngredient(dict: documentIngredient.data() as Dictionary<String,AnyObject>)
ingredinet.append(newIngredinet)
print("ingredinet")
}
}
}
})
db.collection("RECIPES/\(documentRecipe.documentID)/STAGE").getDocuments(completion: { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for documentStage in querySnapshot!.documents {
self.loadItemsQueue.async(group: self.loadRecipesGroup) {
let newStage = FBStage(dict: documentStage.data() as Dictionary<String,AnyObject>)
stage.append(newStage)
}
}
}
})
db.collection("RECIPES/\(documentRecipe.documentID)/PHOTO").getDocuments(completion: { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for documentDishPhoto in querySnapshot!.documents {
self.loadItemsQueue.async(group: self.loadRecipesGroup) {
let newDishPhoto = FBDishPhoto(dict: documentDishPhoto.data() as Dictionary<String,AnyObject>)
photo.append(newDishPhoto)
}
}
}
})
self.loadRecipesGroup.notify(queue: .main) {
var newRecipe = FBRecipe(dict: documentRecipe.data() as Dictionary<String,AnyObject>)
newRecipe.ingredient = ingredinet
newRecipe.cook = stage
newRecipe.photo = photo
// recipe.append(contentsOf: newRecipe)
print(ingredinet)
print(newRecipe)
}
}
print("DONE")
}
}
}
}
Your done is called immediately because you the calls marked as async executed asynchronously. That means that the program continues execution while those calls are being executed. Once they have results they complete and print what you expect from them. So what happens is your code runs through every statement till the end. And async calls may finish some time after that.
Related
func getStudents() {
var student: Student = Student()
db.collection(StudentViewModel.studentCollection).addSnapshotListener { (querySnapshot, error) in
guard error == nil else {
print("ERROR | Getting Student Documents in Firestore Service: \(String(describing: error))")
return
}
guard let snapshot = querySnapshot else {
// no documents
print("No documents to fetch!")
return
}
DispatchQueue.main.sync {
var updatedStudentDocuments: [Student] = [Student]()
for studentDocument in snapshot.documents {
student = Student(id: studentDocument.documentID,
name: studentDocument.data()["name"] as? String ?? "")
updatedStudentDocuments.append(student)
}
self.students = updatedStudentDocuments
}
}
}
Every time I run this function and check what's inside self.students, I find that it's empty. That's because the function getStudents is returning before the closure in addSnapshotListener finishes executing. How do I make the getStudents function wait for the closure to finish executing before continuing its own execution?
I continue to develop a directory application and now I need to delete a specific line in the map from cloud firestore.There is my database:
screenshot
I need to remove field with key "2324141" or change its value.
I know about this method:
"2324141": FieldValue.delete()
But how do I get into the map and delete field or change the value?
thanks!
Here is my model of Object:
class ObjectInFB: Codable {
var objectFromPartsCatalogueListCode: String?
var countOfCurrentObjectInShoppingCart: Int = 1}
Here is func, where i save object in users shoppingBag:
func addToUserShoppingCartFB(user: User?, object: ObjectInFB, count: Int){
guard let user = user else { return }
let objectReadyToBeWriten = [
"UserEmail":"\(user.email!)",
"shoppingCart" : ["\(object.objectFromPartsCatalogueListCode!)" : FieldValue.increment(Int64(count))]] as [String : Any]
db.collection("users")
.document("\(user.uid)")
.setData(objectReadyToBeWriten, merge: true)
{ err in
if let err = err {
print("Error adding document: \(err)")
} else {}
}
}
And after the user in their cart in the application clicks "remove from cart" I want to delete an object from their cart in Firebase. I can only remove any field from the document, but how can I remove a field from the nested ShoppingCart?
func removeShoppingCartFB(object: ObjectInFB, user: User?){
db.collection("users").document(user!.uid).updateData([
"shoppingCart": ["\(object.objectFromPartsCatalogueListCode!)" : FieldValue.delete()],
]) { err in
if let err = err {
print("Error updating document: \(err)")
} else {
print("Document successfully updated")
}
}
}
If the goal is to delete a field within a map, it can be accessed through dot notation. For example suppose your structure looks like this
users
uid_0
UserEmail: "test#test.com"
shoppingCart (a map)
2324141: 10
122323424: 13
and we want to remove the 2324141 field. Here's the Swift code to do that
let myCollection = self.db.collection("users")
let myDoc = myCollection.document("uid_0")
myDoc.updateData( ["shoppingCart.2324141": FieldValue.delete(),
]) { err in
if let err = err {
print("Error updating document: \(err)")
} else {
print("Document successfully updated")
}
}
I am trying to listen for any notifications whenever someone has replied to a post that a user has commented on. Below is how my database structure looks.
Posts: (Collection)
Post 1: (Document)
replies: [user2, user3]
Replies: (Collection)
Reply 1: (Document)
ownerId: [user2]
Reply 2: (Document)
ownerId: [user3]
Currently my code has 2 snapshot listeners. The first one listens to the Posts collections, where a user is inside the 'replies' array. Then the second one listens to the Replies collection, where it returns all documents added that != the current user. When a new reply has been detected, it will set the Tab Bar item's badge.
This currently works right now, but I am curious if there is a better method of doing so.
func getNotifications() {
database.collection("Posts")
.whereField("replies", arrayContains: userData["userId"]!)
.order(by: "timestamp", descending: true)
.limit(to: 70)
.addSnapshotListener() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
}
else {
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(err!)")
return
}
snapshot.documentChanges.forEach { documentd in
if (documentd.type == .added) {
let dataTemp = documentd.document.data()
let ifUser = dataTemp["ownerId"] as! String
if(ifUser == self.userData["userId"]!) {
database.collection("Posts")
.document(documentd.document.documentID)
.collection("Replies")
.whereField("timestamp", isGreaterThan: dataTemp["timestamp"] as! Int)
.addSnapshotListener() { (querySnapshot3, err) in
if let err = err {
print("Error getting documents: \(err)")
}
else {
guard let snapshot = querySnapshot3 else {
print("Error fetching snapshots: \(err!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) {
let temp = diff.document.data()
if((temp["ownerId"] as! String) != self.userData["userId"]!) {
print("new reply")
newArr.append(diff.document.data())
let data = diff.document.data()
let firebaseTime = data["timestamp"] as! Int
let date = lround(Date().timeIntervalSince1970)
if(firebaseTime+10 > date) {
self.tabBar.items![2].badgeValue = "●"
self.tabBar.items![2].badgeColor = .clear
self.tabBar.items![2].setBadgeTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.red], for:.normal)
}
}
}
}
}
}
}
else {
database.collection("Posts")
.document(documentd.document.documentID)
.collection("Replies")
.whereField("ownerId", isEqualTo: self.userData["userId"]!)
.order(by: "timestamp", descending: false)
.limit(to: 1)
.getDocuments() { (querySnapshot2, err) in
if let err = err {
print("Error getting documents: \(err)")
}
else {
var timestamp = Int()
for documentde in querySnapshot2!.documents {
let temp = documentde.data()
timestamp = temp["timestamp"] as! Int
database.collection("Posts")
.document(documentd.document.documentID)
.collection("Replies")
.whereField("timestamp", isGreaterThan: timestamp)
.addSnapshotListener() { (querySnapshot3, err) in
if let err = err {
print("Error getting documents: \(err)")
}
else {
guard let snapshot = querySnapshot3 else {
print("Error fetching snapshots: \(err!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) {
let temp = diff.document.data()
if((temp["ownerId"] as! String) != self.userData["userId"]!) {
print("new reply")
newArr.append(diff.document.data())
let data = diff.document.data()
let firebaseTime = data["timestamp"] as! Int
let date = lround(Date().timeIntervalSince1970)
if(firebaseTime+10 > date) {
self.tabBar.items![2].badgeValue = "●"
self.tabBar.items![2].badgeColor = .clear
self.tabBar.items![2].setBadgeTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.red], for:.normal)
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
Your code does not have only two listeners, but one listener per post for which the user you are interested in has ever replied for. This will lead to terrible performances very soon and will potentially crash you app as Firestore has a limitation of 100 listeners per client.
I would advise to redesign your data model:
Only one listener on posts that the user has ever replied to (your first listener)
On each reply increment a reply counter in the post doc, this will trigger the snapshot above.
Optimisation 1: on each action on a post you could set a field lastactiontype, which would have a specific value reply for replies only. This way the snapshot is only triggered on replies.
Optimisation 2: set a field timestamp on each action to the current time and only pull the last n (for instance 10) posts in your snapshots, this will limit the number of reads at load time. You will have to implement some special logic to handle the case when your app goes offline and back online and all n posts of the snapshot have changed. This is a must if your app is meant to scale (you dont want a snapshot with no limit on a collection with 100k docs...)
Example:
firestore.collection("Posts")
.where( "lastaction", "==" , "reply")
.where( "replies", "array-contains", uid)
.orderBy("timestamp", "desc").limit(10)
I'm trying to get Document Data and Reference Data at the same time, and put data in to custom struct which consist with String Array.
When I run code below, only DocumentReference type "item1_" and "item2_" are appended.
Collection "section" has several documents. Each document has 2 Document references.
I can get right DocumentReference but I can't read it's data in same function.
How can I get both Data from Firestore?
func getall_sec(top: String, mid: String){ref.collection("top_hierarchy").document(top).collection("mid_hierarchy").document(mid).collection("section").addSnapshotListener(){ (snap, err) in
guard let docs = snap else {
self.nosecs = true
return
}
if docs.documentChanges.isEmpty{
self.nosecs = true
return
}
docs.documentChanges.forEach { (doc) in
if doc.type == .added{
let item1_ = doc.document.data()["item1"] as! DocumentReference
let item2_ = doc.document.data()["item2"] as! DocumentReference
item2_.getDocument(){ (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
self.item2_name = querySnapshot?.data()?["low"] as! String
self.item2_ImageName = querySnapshot?.data()?["ImageName"] as! String
}
}
item1_.getDocument(){ (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
self.item1_name = querySnapshot?.data()?["low"] as! String
self.item1_ImageName = querySnapshot?.data()?["ImageName"] as! String
}
}
self.sections.append(SectionModel(id: doc.document.documentID, item_name1: self.item1_name, item_ImageName1: self.item1_ImageName, item_name2: self.item2_name, item_ImageName2: self.item2_ImageName))
}
}
}
}
The main issue is that getDocument is asynchronous and the the self.sections.append code will actually execute before the code in the closures following the two get functions.
item2_.getDocument(){ (querySnapshot2, err2) in
//this code will execute at some point
}
item1_.getDocument(){ (querySnapshot1, err1) in
//this code will execute at some point
}
//this code will execute before the code in the two above getDocument closures.
self.sections.append(SectionModel(id: doc.document.documentID...
one solution is to nest the calls so they execute in order (this is not the best solution but demonstrates the concept)
item2_.getDocument(){ (querySnapshot, err) in
item1_.getDocument(){ (querySnapshot, err) in
self.sections.append(SectionModel(id: doc.document.documentID...
}
}
I want to delete the user account and all of its documents in Firestore, but with asynchronous queries, Firebase deletes the account before all documents.
Because of that I get an auth error because when firestorm delete the lasts documents, the user no longer exist.
db.collection("users").document(self.user.uid).collection("sachets").getDocuments() { (QuerySnapshot, err) in
if let err = err{
print("Erreur de lecture : \(err)")
} else {
for document in QuerySnapshot!.documents {
db.collection("users").document(self.user.uid).collection("sachets").document(document.documentID).delete(){ err in
if let err = err {
print(" 🔴 Probleme de suppression des docuemnts \(err)")
} else {
print(" 🔵 Documents supprimés")
}
}
}
}
}
self.user?.delete { error in
if let error = error {
print(" 🔴 Probleme de suppression du compte Utilisateur \(error)")
} else {
print(" 🔵 Utilisateur supprimé")
}
}
Someone can tell me how to do ?
thanks
Alright, I set up a test-project and solve this. You also need to make sure to remove all the data from the user fields. For instance, if you're having name, age etc... at the user node.
First I created a function to fetch the current users uid:
func currentUser() -> String {
return Auth.auth().currentUser!.uid
}
Then I created a function to remove all the data for the logged in user. This will first remove all the subcollection data and then it will remove the document for the userID.
To handle asynchronous methods you can use DispatchGroup to notify when all the asynchronous calls are done.
One side note: Make sure to NEVER force-unwrap a value if you can't 100% guarantee there are documents. Otherwise, your app will likely crash. To solve this either use guard or if let to solve this problem.
// Where "user" is the result from currentUser()
func removeData(from user: String) {
db.collection("users").document(user).collection("sachets").getDocuments { (snapshot, error) in
if let error = error {
// Handle error
} else if let documents = snapshot?.documents {
// Using if let to see if there are documents
// Time to delete all the subCollection for the user
self.deleteSubCollectionData(for: user, documents, completion: {
// Once that done, make sure to delete all the fields on the highest level.
self.db.collection("users").document(user).delete(completion: { (error) in
if let error = error {
// Handle error
} else {
// Delete the account
self.deleteAccount()
}
})
})
}
}
}
// This function will remove the subCollectionData
fileprivate func deleteSubCollectionData(for user: String, _ documents: [QueryDocumentSnapshot], completion: #escaping () -> ()) {
let group = DispatchGroup()
documents.forEach({
group.enter()
self.db.collection("users").document(user).collection("sachets").document($0.documentID).delete(completion: { (error) in
if let error = error {
// Handle error
}
group.leave()
})
})
// Once the dispatchGroup is done...
group.notify(queue: .main) {
completion()
}
}
And in the end...
func deleteAccount() {
Auth.auth().currentUser?.delete { (error) in
if let error = error {
print(error)
} else {
print("Deleted account")
}
}
}
If you don't remove all the levels of data, then there still will be data in Firestore.
Your getDocuments() method is asynchronous, so you should delete the account only after having deleted the documents.
Just put the user?.delete method inside the getDocuments() callback
db.collection("users").document(self.user.uid).collection("sachets").getDocuments() { (QuerySnapshot, err) in
if let err = err{
print("Erreur de lecture : \(err)")
} else {
for document in QuerySnapshot!.documents {
db.collection("users").document(self.user.uid).collection("sachets").document(document.documentID).delete(){ err in
if let err = err {
print(" 🔴 Problème de suppression des documents \(err)")
} else {
print(" 🔵 Documents supprimés")
}
}
}
}
self.user?.delete { error in
if let error = error {
print(" 🔴 Problème de suppression du compte Utilisateur \(error)")
} else {
print(" 🔵 Utilisateur supprimé")
}
}
}