Firebase 和 swiftUI,监听实时更新奇怪的行为怪异

Posted

技术标签:

【中文标题】Firebase 和 swiftUI,监听实时更新奇怪的行为怪异【英文标题】:Firebase and swiftUI, listening for real time update strange behave weird 【发布时间】: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 的方式是否正确? 或任何其他想法如何处理好友请求。很高兴改变我处理好友请求的方式。

谢谢

【问题讨论】:

【参考方案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 案例时才有效,但有时我仍然会得到两倍或更多的结果(无法找出原因),我在此链接上发布了问题:***.com/questions/63214945/… 我完全不明白为什么 onSucces 不能在 forEach 之外工作 嗨,我不明白,当您加载视图并执行您的 Api 时,onSuccess 在 forEach 之外,首先是让您的所有用户等待添加(案例)当 forEach 通过 onSuccess 完成其发送时转义到你的视图模型。考虑到您的问题,我遇到了类似的问题,因为在之前调用 View 时,在您的 ViewModel 中放置一个 init 函数并调试或打印对我有帮助,然后我多次看到我的视图初始化,因为之前的 View 有问题。跨度>

以上是关于Firebase 和 swiftUI,监听实时更新奇怪的行为怪异的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI / Firebase:我可以使用@Published 向 Firebase 数据库发送和接收单个值吗?

异步firebase调用后SwiftUI不更新变量

SwiftUI - 在 Firebase 上更新密码

SwiftUI - 更新 Firebase 实时数据库上的数据

如何使用 SwiftUI 更新 Firebase 中的集合?

在 SwiftUI 中仅更新来自 Firebase 的新数据