首页 > 解决方案 > 设计问题回复:异步 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()
                                    })
                            }
                        }

标签: asynchronousgoogle-cloud-firestoreswiftuireal-time

解决方案


推荐阅读