子集合的 Firestore 监听器

Posted

技术标签:

【中文标题】子集合的 Firestore 监听器【英文标题】:Firestore listener for sub collections 【发布时间】:2019-04-13 02:53:45 【问题描述】:

我通过以下方式设置 Firestore:

Channels [collection] ----> channelID ---> Messages [collection] ---> 消息ID

如何将 snapshotListener 添加到子集合“消息”?

  Firestore.firestore().collection("Channels").document().collection("Messages").addSnapshotListener  (querySnapshot, error) in
        guard let snapshot = querySnapshot else 
            print("Error listening for channel updates: \(error?.localizedDescription ?? "No error")")
            return
        

        snapshot.documentChanges.forEach  change in 
           print(change)
        
    

这对我不起作用

【问题讨论】:

为什么你说它不起作用?你有错误吗? 添加新消息时,不会调用此监听器 在这种情况下,请添加更多代码。 我更新了帖子 【参考方案1】:

我想是因为有了

Firestore.firestore().collection("Channels").document().collection("Messages")

您没有定义正确的CollectionReference,因为您没有识别“Channels”集合的文档。

你应该这样做:

Firestore.firestore().collection("Channels").document(channelID).collection("Messages")

【讨论】:

这不是我想要的,使用你的代码我只会收听一个 id == channelID 的频道,但我想收听所有频道 更新:见 Doug 的回答。你想要的是不可能的。如果您收听Firestore.firestore().collection("Channels"),该文档说“每次查询结果更改时(即添加、删除或修改 document 时),快照处理程序都会收到一个新的查询快照” . 好的,所以我认为最好的解决方案是将消息移动为根集合并在消息内部添加字段 channelID 是的,这将是一个解决方案。【参考方案2】:

您不能让单个侦听器接收来自未知数量的子集合的更新。集合上的侦听器没有“通配符”运算符。您必须选择一个特定的集合或查询并为其附加一个侦听器。

【讨论】:

【参考方案3】:

正如 Doug 在他的正确答案中指出的那样,您不能让单个侦听器接收来自未知(数量或未指定)子集合的更新。

但是,如果您可以确定这些子集合名称,那么答案就很简单了。

这个想法是读取 Channels 的子节点,它们将是 channel_0、channel_1 等,并使用这些文档 id 来构建对您有兴趣收听的节点的引用。

因此,鉴于这种结构(与问题中的结构相匹配):

Channels
  channel_0
    Messages
      message_0
        msg: "chan 0 msg 0"
      message_1
        msg: "chan 0 msg 1"
      message_2
        msg: "chan 0 msg 2"
  channel_1
    Messages
      message_0
        msg: "chan 1 msg 0"
      message_1
        msg: "chan 1 msg 1"

这是为每个通道添加侦听器并响应该通道消息中的事件的代码,该消息在控制台中通知消息 ID、消息文本和事件发生的通道。

func addChannelCollectionObserver() 
    let channelsRef = self.db.collection("Channels")
    channelsRef.getDocuments(completion:  snapshot, error in

        guard let documents = snapshot?.documents else 
            print("Collection was empty")
            return
        

        for doc in documents 
            let docID = doc.documentID
            let eachChannelRef = channelsRef.document(docID)
            let messagesRef = eachChannelRef.collection("Messages")

            messagesRef.addSnapshotListener  querySnapshot, error in

                querySnapshot?.documentChanges.forEach  diff in
                    if diff.type == .added 
                        let doc = diff.document
                        let msgId = doc.documentID
                        let channelId = messagesRef.parent!.documentID
                        let msg = doc.get("msg") as? String ?? "no message"
                        print(" added msgId: \(msgId) with msg: \(msg) in channel: \(channelId)")
                    

                    if diff.type == .modified 
                        let doc = diff.document
                        let msgId = doc.documentID
                        let msg = doc.get("msg") as? String ?? "no message"
                        print(" modified msgId: \(msgId) with msg: \(msg)")
                    

                    if diff.type == .removed 
                        let doc = diff.document
                        let msgId = doc.documentID
                        let msg = doc.get("msg") as? String ?? "no message"
                        print(" removed msgId: \(msgId) with msg: \(msg)")
                    
                
            
        
    )

首次运行时,输出将按预期显示每个子节点。从那时起,它将输出任何添加、修改或删除。

 added msgId: message_0 with msg: chan 0 msg 0 in channel: channel_0
 added msgId: message_1 with msg: chan 0 msg 1 in channel: channel_0
 added msgId: message_2 with msg: chan 0 msg 2 in channel: channel_0
 added msgId: message_0 with msg: chan 1 msg 0 in channel: channel_1
 added msgId: message_1 with msg: chan 1 msg 1 in channel: channel_1

代码还需要对某些选项进行一些额外的错误检查,但它应该提供解决方案。

【讨论】:

@anoop4real 性能非常好,不能说它是“建议的”,但是它确实有效并提供了预期的结果。一般来说 - 这是一个建议的解决方案,所以我会试一试。 感谢您的回复,我遇到了一个疯狂的情况,我在这里发布了它..groups.google.com/forum/#!topic/google-cloud-firestore-discuss/… ...您可以看看,看看您是否可以提出任何建议? 此解决方案不可扩展,这样您只能启动 100 个侦听器。您应该使用 Collection-group-query firebase.google.com/docs/firestore/query-data/… @akhil 我们有成千上万的听众在使用它,没有任何问题。为什么认为有100个限制?是的集合组,但在 2018 年(发布时),该功能没有得到很好的开发。【参考方案4】:

为此使用collection group query,监听所有同名的集合

    db.collectionGroup("Messages"). docRef.addSnapshotListener(new EventListener<DocumentSnapshot>() 
  @Override
  public void onEvent(@Nullable DocumentSnapshot snapshot,
                      @Nullable FirestoreException e) 
    if (snapshot != null && snapshot.exists()) 
      System.out.println("Current data: " + snapshot.getData());
     else 
      System.out.print("Current data: null");
    
  
);

【讨论】:

这应该是公认的答案。谢谢。【参考方案5】:

如果您想在后端订阅 Firstore 子集合更改,这里是来自 Firebase docs 的 NodeJS 示例:

// Listen for changes in all documents in the 'users' collection and all subcollections
exports.useMultipleWildcards = functions.firestore
    .document('users/userId/messageCollectionId/messageId')
    .onWrite((change, context) => 
      // If we set `/users/marie/incoming_messages/134` to body: "Hello" then
      // context.params.userId == "marie";
      // context.params.messageCollectionId == "incoming_messages";
      // context.params.messageId == "134";
      // ... and ...
      // change.after.data() == body: "Hello"
    );

请注意,即使是子集合名称也可以与通配符 messageCollectionId 一起使用,即我们可以订阅用户集合的任何子集合

根据问题Channels [collection] ----&gt; channelID ---&gt; Messages [collection] ---&gt; messageID代码可以是:

exports.onMessagesSubcollectionChange = functions.firestore
    .document('Channels/channelID/Messages/messageID')
    .onWrite((change, context) => 
   // the rest code
   // context.params.channelID
   // and context.params.messageID will be fulfilled.

不确定客户端是否支持。

【讨论】:

以上是关于子集合的 Firestore 监听器的主要内容,如果未能解决你的问题,请参考以下文章

读取带有子集合的文档的 Firestore 计费

Cloud Firestore 如何查找包含特定文档 ID 的子集合的所有文档?

Firebase Firestore 子集合

Google Pub/Sub 更新 Firestore 子集合

Firebase Firestore 读取和定价

Firestore 分页数据 + 快照监听器