On my project I'm using firebase Cloud Firestore in order to implement a similar system like "Facebook" to request and confirm friends.
On my app some user are Administrator and some just normal user.
Only normal user can search and add Administrator in order to join some specific event scheduled from the administrator.
here below how is my data in Cloud Firestore :
every user have collection with pending and confirm friends.
I' m using a view to list all the pending and confirm friends for each user
using the .addSnapshotListener {} of firebase I'm checking with the following function every time there is a change on the confirm and pending friend and I publish the change in 2 @Published arrays, pendingFriendsADMIN = [UserMOdel] and confirmedFriendADMIN = [UserMOdel]
func userUpdateFriendUser(userInfo: UserModel){ db.collection("userUser").document(userInfo.email).collection("pendingFriends") .addSnapshotListener(includeMetadataChanges: false) { documentSnapshot, error in self.pendingFriendsUSER = [] guard let documents = documentSnapshot?.documents else { print("Error fetching documents: \(String(describing: error?.localizedDescription))") return } var i = 0 for doc in documents { debugPrint("inizio il ciclo pending user\(i)") let idUser = doc["userID"] as? String ?? "no ID" self.downloadImageForAdmin(userID: idUser) { (urlImage) in let userPending = UserModel(name: "", surname: "" ,username: "", email: "", userID: "", adminLevel: "", immagine: urlImage!, position: "centro", position2: "sx", vote: 0) userPending.name = doc["name"] as? String ?? "NA name" userPending.surname = doc["surname"] as? String ?? "NA surname" userPending.adminLevel = doc["adminLevel"] as? String ?? "NA admin" userPending.email = doc["email"] as? String ?? "NA email" userPending.username = doc["username"] as? String ?? "NA username" userPending.userID = doc["userID"] as? String ?? "NA id" userPending.position = doc["position"] as? String ?? "na position" userPending.position2 = doc["position2"] as? String ?? "na position" userPending.vote = doc["vote"] as? Int ?? 0 self.pendingFriendsUSER.append(userPending) i = i+1 debugPrint("finito ciclo pending") } } } db.collection("userUser").document(userInfo.email).collection("confirmedFriend") .addSnapshotListener (includeMetadataChanges: false){ documentSnapshot, error in self.confirmedFriendUSER = [] guard let documents = documentSnapshot?.documents else { print("Error fetching documents: \(String(describing: error?.localizedDescription))") return } for doc in documents { debugPrint("inizio il ciclo confirm user \(i)") let idUser = doc["userID"] as? String ?? "no ID" self.downloadImageForAdmin(userID: idUser) { (urlImage) in let userConfirm = UserModel(name: "", surname: "" ,username: "", email: "", userID: "", adminLevel: "", immagine: urlImage!, position: "centro", position2: "sx", vote: 0) userConfirm.name = doc["name"] as? String ?? "NA name" userConfirm.surname = doc["surname"] as? String ?? "NA surname" userConfirm.adminLevel = doc["adminLevel"] as? String ?? "NA admin" userConfirm.email = doc["email"] as? String ?? "NA email" userConfirm.username = doc["username"] as? String ?? "NA username" userConfirm.userID = doc["userID"] as? String ?? "NA id" userConfirm.position = doc["position"] as? String ?? "na position" userConfirm.position2 = doc["position2"] as? String ?? "na position" userConfirm.vote = doc["vote"] as? Int ?? 0 self.confirmedFriendUSER.append(userConfirm) } } } } the similar method is used also for listed the change on the userFriendList.
a user, can search an administrator via the email, and send to him a friend request(see below)
the user sent the friend request with the following function: simply, I write on the pending friend the of the user the admin email and in the admin pending friend the user email
func sendFriendRequest(userInfo: UserModel, userToRequest: UserModel, closure: @escaping warning){ // check if reuqest already sent self.db.collection("userAdmin").document(userToRequest.email).collection("confirmedFriend").whereField("email", isEqualTo: userInfo.email).getDocuments() { (queryResult, err) in if let err = err { debugPrint("unable to get data , friend alrady request\(err)") } else { if queryResult!.documents.count > 0 { debugPrint("siete gia amici") // mettere warning let warning = true closure(warning) return } else { // if request never sent, metto user nella lista dell admin pending self.db.collection("userAdmin").document(userToRequest.email).collection("pendingFriends").document(userInfo.email).setData([ "username": userInfo.username, "email" : userInfo.email, "userID" : userInfo.userID, "adminLevel": userInfo.adminLevel, "name":userInfo.name, "surname":userInfo.surname, "position": userInfo.position, "position2": userInfo.position2, "vote": userInfo.vote ], merge: false) { (err) in self.db.collection("userUser").document(userInfo.email).collection("pendingFriends").document(userToRequest.email).setData([ "username": userToRequest.username, "email" : userToRequest.email, "userID" : userToRequest.userID, "adminLevel": userToRequest.adminLevel, "name":userToRequest.name, "surname":userToRequest.surname, "position": userToRequest.position, "position2": userToRequest.position2, "vote": userToRequest.vote ], merge: false) } // metto sulla mia pending request } } } } Here the problem... some time , not always once I sent the request to a friend admin the .addSnapshotListener duplicate the change , as you can se from the third picture there is 2 time the same pending friend.
If I exit from the view and I go back the pending friend are correct.
here the code of my AdminFriendRequest : View
import SwiftUI import URLImage struct AdminFriendRequest: View { @Binding var dismissView : Bool @ObservedObject var dm : DataManager @Binding var meInfo: UserModel? var body: some View { VStack{ fakebar Spacer() List{ HStack { Image(systemName: "person.2") Text("Pending friends request:") }.font(.headline) .foregroundColor(.blue) ForEach(dm.pendingFriendsADMIN) { friend in HStack{ if friend.immagine == nil{ Image(systemName: "person") .resizable() .frame(width: 30, height: 30, alignment: .center) .clipShape(Circle()) } else { URLImage(friend.immagine!) { proxy in proxy.image .resizable() .frame(width: 30, height: 30, alignment: .center) .clipShape(Circle()) } } Text(friend.username) Spacer() Image(systemName: "checkmark.circle") } .onTapGesture { if self.meInfo != nil { self.dm.tapToConfirmFriend(me: self.meInfo!, friendToConfirm: friend) { (isFriendConfirm) in debugPrint("is friend confirm \(isFriendConfirm)") } } } } if dm.pendingFriendsADMIN.isEmpty { Text("No friend request yet").font(.caption) } HStack { Image(systemName: "person.3") Text("Friends:") }.font(.headline) .foregroundColor(.blue) ForEach(dm.confirmedFriendADMIN) { friend in HStack{ if friend.immagine == nil{ Image(systemName: "person") .resizable() .frame(width: 30, height: 30, alignment: .center) .clipShape(Circle()) } else { URLImage(friend.immagine!) { proxy in proxy.image .resizable() .frame(width: 30, height: 30, alignment: .center) .clipShape(Circle()) } } Text(friend.username) Spacer() Image(systemName: "checkmark.circle").foregroundColor(.green) Button(action: { self.dm.removeFriend(me: self.meInfo!, friendConfirm: friend) }, label: { Text("remove friend") }) }.padding(.all) } }.padding(.trailing) } .onAppear { self.dm.newListUpdateForAdmin(userInfo: self.meInfo!) } } var fakebar: some View { ZStack { HStack { Spacer() Image(systemName: "chevron.compact.down") .font(.system(size: 60)) .aspectRatio(contentMode: .fit) .foregroundColor(.white) Spacer() } HStack { Spacer() Button(action: { self.dismissView.toggle() }) { Text("Close") .fontWeight(.bold) .foregroundColor(.white) .padding(.horizontal) } } } .frame(height: 44) .background(Color.green.padding(.top, -44)) } } I use the onAppear to trigger the list update with the .addSnapshotListener
.onAppear { self.dm.newListUpdateForAdmin(userInfo: self.meInfo!) } I can't find out why... is it correct the way how i'm using the .addSnapshotListener ? Or any other idea how to handle the friend request. happy to change my way how to deal with the friend request.
Thanks


