asynchronous - 设计问题回复:异步 firestore 查询不适用于 firestore 自动对象刷新
问题描述
考虑一个社交应用程序,其中每个用户都可以“加好友”其他用户。
我在 Firestore 中有两个系列。一个包含每个用户的文档,其中存储了朋友 GUID 的列表。看起来像
"<my GUID>": {
myFriendsArray:"[ "<friend1GUID>","<friend2GUID>": "<friend3GUID>"]
}
另一个集合包含实际的用户配置文件。它看起来像这样:
"<friend1GUID>": {
name : "tom smith",
nickname: "tommy",
email : "wer@ggmail.com"
}
"<friend2GUID>": {
name : "bart jones",
nickname: "bj",
email : "dgh@ggmail.com"
}
"<friend3GUID>": {
name : "carol smith",
nickname: "bunny",
email : "abc@ggmail.com"
}
当用户想要查看他的朋友时,我需要查询用户的朋友 GUID(到一个数组中),然后遍历该数组并查询每个朋友的个人资料。显然,我需要等待第一个查询完成,然后才能开始循环播放玩家查询。
这一切都很好,但是,我的 UI 中没有一个实时更新。即如果我添加一个新用户,我必须退出 ShowMyFriends 视图并返回才能看到新条目。删除,更新等相同...
这是我执行两个顺序查询和“添加”功能的模型类。
class PlayerRepository: ObservableObject {
private let store = Firestore.firestore()
private let path: String = "Players"
private let friendPath: String = "MyFriends"
@Published var players: [UserProfile] = []
@Published var friendIDs: [String] = []
init() {
// get the IDs first. When it finishes, use the resulting array to drive the next query.
self.getFriendIDs()
}
// ----------------- query to get the list of friend GUIDs for the current user --------------------
func getFriendIDs() {
let group = DispatchGroup()
group.enter()
store.collection(friendPath).document(Auth.auth().currentUser!.uid)
.addSnapshotListener { [self] (document, error) in
let result = Result {
try document?.data(as: Friends.self)
}
switch result {
case .success(let f):
//print("friends:>> \(f!.myFriends)")
friendIDs = f!.myFriends
let count = group.debugDescription.components(separatedBy: ",").filter({$0.contains("count")}).first!.components(separatedBy: CharacterSet.decimalDigits.inverted).filter({Int($0) != nil})
// leave the group when done
if (count[0] != "0") {
group.leave()
}
case .failure(let error):
print("Error decoding city: \(error)")
let count = group.debugDescription.components(separatedBy: ",").filter({$0.contains("count")}).first!.components(separatedBy: CharacterSet.decimalDigits.inverted).filter({Int($0) != nil})
// leave the group when done
if (count[0] != "0") {
group.leave()
}
}
}
group.notify(queue: DispatchQueue.global(qos: .unspecified)) {
// at this point we know the first query is done. Now go and launch the 2nd query.
print("Just fetched IDS. #: \(self.friendIDs.count)")
self.getFriends()
}
}
// ----------------- loop through each GUID and query each of the player profiles. --------------------
func getFriends() {
// Initialize the DispatchGroup
let group = DispatchGroup()
var p: UserProfile
p = UserProfile(name: "", nickname: "", loginID: "", email: "", createdBy: "") //we can grab createdby over in the add function.
// Enter the group outside of the getDocuments call
print("ok, begin -- --- find \(self.friendIDs.count) players -- ----")
for pid in friendIDs {
group.enter()
store.collection("Players").document(pid).getDocument { (document, error) in
if let document = document, document.exists {
try? p = document.data(as: UserProfile.self)!
self.players.append(p)
//print("just appended player: \(p)")
group.leave()
} else {
print("Document does not exist")
group.leave()
}
}
}
group.notify(queue: DispatchQueue.global(qos: .background)) {
print("Just fetched FRIENDS. #: \(self.players.count)")
//self.players = self.players.removeDuplicates()
}
}
// ----------------- Add a new player --------------------
func add(_ player: UserProfile) {
var newPlayer = player
//var newPlayer = player
var ref: DocumentReference? = nil
// Initialize the DispatchGroup
let group = DispatchGroup()
// Enter the group outside of the getDocuments call
group.enter()
do {
newPlayer.createdBy = Auth.auth().currentUser!.uid
try ref = self.store.collection(path).addDocument(from: newPlayer)
// determine whether or not there is a 'group' in process before we decide to .leave it. dumps core otherwise.
let count = group.debugDescription.components(separatedBy: ",").filter({$0.contains("count")}).first!.components(separatedBy: CharacterSet.decimalDigits.inverted).filter({Int($0) != nil})
// leave the group when done
if (count[0] != "0") {
group.leave()
}
} catch {
fatalError("Unable to add player: \(error.localizedDescription).")
}
// Wait for the new player to be written and then use the document ref and send it to the func to create the friend relationship
group.notify(queue: DispatchQueue.global(qos: .background)) { [self] in
self.guaranteeMainThreadSynchronousExecution {
self.addFriendRecord(newPlayer, ref: ref!)
players.append(newPlayer)
friendIDs.append(ref!.documentID)
}
print("Just added new PLAYER #: \(self.players.count)")
}
}
func guaranteeMainThreadSynchronousExecution(_ block: () -> ()) {
if Thread.isMainThread {
block()
} else {
DispatchQueue.main.sync {
block()
}
}
}
现在这里是视图的相关部分,我在其中显示了用户朋友的列表。
struct PlayerListView: View {
@ObservedObject var playerRepository = PlayerRepository()
@EnvironmentObject var authState: AuthenticationState
@State var showAddPlayerForm = false
@EnvironmentObject var myDisplayControlCenter: DisplayControlCenter
@State var showDeleteAlert = false
@State var showCopyView = false
@State var deleteIndex = 0
@State var deleteThisPlayer : [UserProfile] = []
@State private var st = false
var body: some View {
NavigationView {
VStack {
GeometryReader { geometry in
List {
Section(header: Text("Players I Have Created")) {
ForEach(playerRepository.players.sorted().filter({
"\($0.createdBy)" == self.authState.loggedInUser!.uid
}),id: \.self)
{ player in
VStack(alignment: .leading, spacing: 5) {
Text("\(player.name)")
.fontWeight(.regular)
.font(.system(size: 16))
Text("\(player.nickname)")
.fontWeight(.light)
.font(.system(size: 12))
Text("\(player.email)")
.fontWeight(.light)
.font(.system(size: 12))
Text("\(player.id!)")
.font(.system(size: 14))
NavigationLink(destination: PlayerDetailView(playerRepository: playerRepository, player: player, index: deleteIndex, editMode: 1)) {}
}
}
.onDelete(perform: self.triggerDeleteAlert) // call a small func to trigger the alert below
.alert(isPresented: $showDeleteAlert) {
Alert(
title: Text("Remove Player"),
message: Text("Are you sure you want to remove this player?: \(deleteThisPlayer[0].name)"),
primaryButton: .destructive(Text("Remove")) {
self.playerRepository.remove(deleteThisPlayer[0])
print("removing: \(deleteThisPlayer[0].name)")
deleteThisPlayer.removeAll()
},
secondaryButton: .cancel() {
print("remove cancelled")
deleteThisPlayer.removeAll()
})
}
}
解决方案
推荐阅读
- node.js - 节点js上的错误消息之前的奇怪文字
- c# - c# windows form app,替换字符串的问题
- android - 我无法将图像从设备上传到 Firebase 存储(kotlin)
- ios - Nativescript - 应用程序的 Info.plist 不包含 CFBundleVersion
- spring - Spring mongo如何检索数据但首先是最新日期
- javascript - 猫鼬和Node Js中线程注释的数据结构
- flutter - Image_picker => Permission Denial: uri content://media/external/file from requires android.permission.WRITE_EXTERNAL_STORAGE, or grantUriPermission()
- matlab - 如何计算 12 个方向的最大响应?
- javascript - 使用 React.js 在 OpenLayers 中叠加
- javascript - 使用上下文 api 提升状态是一种好习惯吗?