【问题标题】:Firebase and swiftUI, listening for real time update strange behave weirdFirebase 和 swiftUI,监听实时更新奇怪的行为怪异
【发布时间】:2020-07-25 12:50:37
【问题描述】:

在我的项目中,我使用 firebase Cloud Firestore 来实现类似“Facebook”的系统来请求和确认朋友。

在我的应用中,一些用户是管理员,一些只是普通用户。

只有普通用户才能搜索和添加管理员,以便加入管理员安排的某些特定活动。

下面是我在 Cloud Firestore 中的数据:

每个用户都有待处理和确认的朋友的收藏。

我正在使用视图为每个用户列出所有待定和确认的朋友

使用 firebase 的 .addSnapshotListener {} 每次确认好友和待处理好友发生更改时,我都会使用以下函数进行检查,并在 2 个 @Published 数组中发布更改,pendingFriendsADMIN = [UserMOdel] 和 confirmFriendADMIN = [用户模型]

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

类似的方法也用于列出userFriendList上的变化。

用户,可以通过邮件搜索管理员,并向他发送好友请求(见下文)

用户使用以下函数发送好友请求: 简单地说,我在用户的待处理朋友上写了管理员电子邮件,在管理员待处理朋友中写了用户电子邮件

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

这里的问题... 有时,并非总是在我向朋友管理员发送请求时,.addSnapshotListener 会重复更改,正如您从第三张图片中看到的那样,有 2 次相同的待处理朋友。

如果我退出视图并返回,待处理的朋友是正确的。

这里是我的 AdminFriendRequest 的代码:查看

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

我使用 onAppear 通过 .addSnapshotListener 触发列表更新

.onAppear {
            self.dm.newListUpdateForAdmin(userInfo: self.meInfo!)

        }

我不知道为什么...我使用 .addSnapshotListener 的方式是否正确? 或任何其他想法如何处理好友请求。很高兴改变我处理好友请求的方式。

谢谢

【问题讨论】:

    标签: arrays firebase google-cloud-firestore swiftui


    【解决方案1】:

    一些可能有帮助的建议:

    1.实现侦听器状态控制:这些有助于控制用户何时添加、修改或删除记录,有助于不重新加载所有数据并允许不重复事件,例如在下面的代码中我得到所有用户(事件 documentChange .add) 如果添加了新用户,则不会重新加载所有用户数组。

    API:

     function getUsersPending(userInfo: UserModel, onSuccess: @escaping([UserModel]) -> Void, onError: @escaping(_ errorMessage: String) -> Void, newPendingUser: @escaping(UserModel) -> Void ) {
    
         db.collection("userUser").document(userInfo.email).collection("pendingFriends").addSnapshotListener(includeMetadataChanges: false) { documentSnapshot, error in
                self.pendingFriendsUSER = []
                
          guard let snapshot = documentSnapshot else { return }
          var userPendingArray = [UserModel]()
          snapshot.documentChanges.forEach { (documentChange) in
            switch documentChange.type {
              case: .added :
                 let dict = documentChange.document.data()
                 //Get User from firebase doc pendingUser = ....
                 newPendingUser(pendingUser) //escape New User
                 userPendingArray.appen(pendingUser)
                 print("Pending User Added")
              case .modified :
                 //implements action (new escaping)
                 print("Pending User Modified") 
              case .removed :
                 print("User pending removed")
            }
          }
          onSuccess(userPendingArray)
            
        }
    

    用户等待 ViewModel 示例

    class UserPendingViewModel() : ObservableObject {
      
      @Published var usersPending: [UserModel] = []
      @Published var isLoading = false
      var errorString : String = ""
    
      func loadUsersPending() {
         self.usersPending = []
         self.isLoading = true
         
        dm. getUsersPending(userInfo: userModel, onSuccess: { (users) in
            if (self.usersPending.isEmpty) { self.usersPending = users }
            self.isLoading = false
        }, onError: { (errorMessage) in
            print("Error Message \(errorMessage)")
        }, newPendingUser: { (user) in
            if (!self.usersPending.isEmpty) { self.usersPending.append(user) }
        }) 
        
      }
    }
    

    查看

    struct UserPendingView: View {
    
       @ObservedObject var model = UserPendingViewModel()
       
       var body: some View {
    
             ScrollView {
                
                    if !model.usersPending.isEmpty {
                        ForEach(model.usersPending, id: \.messageId) { user in
                            //Show your data
                            }
                        }
                    }.onAppear{ self.model.loadUsersPending() }
               
             }
    

    2。激活/停用监听器。如果您的应用未显示待处理视图,则用户无需保持活动侦听器。激活监听器 onAppear 和 Deactivate onDisappear。

    上一个示例新转义、var 监听器声明和 Api 上的结果

    function getUsersPending(userInfo: UserModel, onSuccess: @escaping([UserModel]) -> Void, onError: @escaping(_ errorMessage: String) -> Void, newPendingUser: @escaping(UserModel) -> Void, listener: @escaping(_ listenerHandle: ListenerRegistration) -> Void ) { ) {
    
         let listenerRegistration = db.collection("userUser").document(userInfo.email).collection("pendingFriends").addSnapshotListener(includeMetadataChanges: false) { documentSnapshot, error in
                self.pendingFriendsUSER = []
                
          guard let snapshot = documentSnapshot else { return }
          var userPendingArray = [UserModel]()
          snapshot.documentChanges.forEach { (documentChange) in
            switch documentChange.type {
              case: .added :
                 let dict = documentChange.document.data()
                 //Get User from firebase doc pendingUser = ....
                 newPendingUser(pendingUser) //escape New User
                 userPendingArray.appen(pendingUser)
                 print("Pending User Added")
              case .modified :
                 //implements action (new escaping)
                 print("Pending User Modified") 
              case .removed :
                 print("User pending removed")
            }
          }
          onSuccess(userPendingArray)
        } 
      listener(listenerRegistration)  //escaping listener
    }
    

    用户等待 ViewModel 示例、声明监听器和添加函数监听器结果(注意:导入 Firebase)

    class UserPendingViewModel() : ObservableObject {
      
      @Published var usersPending: [UserModel] = []
      @Published var isLoading = false
      var errorString : String = ""
      var listener : ListenerRegistration!
    
      func loadUsersPending() {
         self.usersPending = []
         self.isLoading = true
         
        dm. getUsersPending(userInfo: userModel, onSuccess: { (users) in
            if (self.usersPending.isEmpty) { self.usersPending = users }
            self.isLoading = false
        }, onError: { (errorMessage) in
            print("Error Message \(errorMessage)")
        }, newPendingUser: { (user) in
            if (!self.usersPending.isEmpty) { self.usersPending.append(user) }
        }) { (listener) in
            self.listener = listener
        } 
        
      }
    }
    

    View 实现 onDisappear 断开监听

    struct UserPendingView: View {
    
       @ObservedObject var model = UserPendingViewModel()
       
       var body: some View {
    
             ScrollView {
                
                    if !model.usersPending.isEmpty {
                        ForEach(model.usersPending, id: \.messageId) { user in
                            //Show your data
                            }
                        }
                    }.onAppear{ self.model.loadUsersPending() }
                     .onDisappear {
                         if self.model.listener != nil {
                            self.model.listener.remove()
                         }
                    }
               }
    

    【讨论】:

    • 您好,感谢您的帮助,使用您的答案我想出了一个解决方案,但是我在关闭 onSuccess 时遇到了一些问题,如果我像您的示例一样我看不到更新,它只有在我放入 . added 案例时才有效,但有时我仍然会得到两倍或更多的结果(无法找出原因),我在此链接上发布了问题:stackoverflow.com/questions/63214945/…
    • 我完全不明白为什么 onSucces 不能在 forEach 之外工作
    • 嗨,我不明白,当您加载视图并执行您的 Api 时,onSuccess 在 forEach 之外,首先是让您的所有用户等待添加(案例)当 forEach 通过 onSuccess 完成其发送时转义到你的视图模型。考虑到您的问题,我遇到了类似的问题,因为在之前调用 View 时,在您的 ViewModel 中放置一个 init 函数并调试或打印对我有帮助,然后我多次看到我的视图初始化,因为之前的 View 有问题。跨度>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-11-15
    • 1970-01-01
    • 2022-01-07
    • 2020-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多