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 数据库发送和接收单个值吗?
SwiftUI - 更新 Firebase 实时数据库上的数据