0

I am currently working on an app and I'm trying to implement a chat system. I am trying to get the number of unread messages by first checking to see if the chat room is active, and if it is, get the last message sent in each chat room and if it's unread, add to the counter. The issue however, is that when I make another query call inside of the first query, it does not run. This is what I have currently.

static Stream<int> getUnreadMessagesCountStream(String userId) {
    final FirebaseFirestore firestore = FirebaseFirestore.instance;

    return firestore.collection('chat_rooms').snapshots().map((querySnapshot) {
      int unreadCount = 0;
      for (QueryDocumentSnapshot<Map<String, dynamic>> document
          in querySnapshot.docs) {
        Map<String, dynamic> chatSettings = document.data();
        if (chatSettings['active'] == true) {
          firestore
              .collection('chat_rooms')
              .doc(document.id)
              .collection("messages")
              .orderBy('timestamp', descending: true)
              .limit(1)
              .snapshots()
              .map((messagesSnapshot) {
            Map<String, dynamic> lastMessage =
                messagesSnapshot.docs.first.data();
            if (lastMessage['read'] == false &&
                lastMessage['receiver_id'] == userId) {
              unreadCount++;
            }
          });
        }
      }

      return unreadCount;
    });
  }

The first query call return firestore.collection('chat_rooms').snapshots().map((querySnapshot) { works fine, but the inner call

firestore
              .collection('chat_rooms')
              .doc(document.id)
              .collection("messages")
              .orderBy('timestamp', descending: true)
              .limit(1)
              .snapshots()
              .map((messagesSnapshot) {
            Map<String, dynamic> lastMessage =
                messagesSnapshot.docs.first.data();
            if (lastMessage['read'] == false &&
                lastMessage['receiver_id'] == userId) {
              unreadCount++;
            }
          });

does not.

My Firebase layout is as followsFirestore layout The messages sub collection is just a collection of documents Messages sub collection

I've tried saving everything in one document, the settings and the messages, but I ran into the problem of messages being dropped if two users sent a message at the same time. I also tried using a StreamBuilder from the async package and Rx. I've also tried using collectionGroup instead of collection but then I wasn't able to access the document with the activity status.

Any help would be much appreciated.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
gdybdy
  • 1

1 Answers1

1

Yes! the below code will not work in your case

firestore
              .collection('chat_rooms')
              .doc(document.id)
              .collection("messages")
              .orderBy('timestamp', descending: true)
              .limit(1)
              .snapshots()
              .map((messagesSnapshot) {
            Map<String, dynamic> lastMessage =
                messagesSnapshot.docs.first.data();
            if (lastMessage['read'] == false &&
                lastMessage['receiver_id'] == userId) {
              unreadCount++;
            }
          });

Because the above is snapshot (Stream). and you have to merge all stream in order to work as expected.

Solution

Below solution works but this is not fully stream. Because I used get(). By using get() I can fetch all document aync and count it.

static Stream<int> getUnreadMessagesCountStream(String userId) {
    final FirebaseFirestore firestore = FirebaseFirestore.instance;

    return firestore
        .collection('chat_rooms')
        .snapshots()
        .asyncMap((querySnapshot) async {
      int unreadCount = 0;
      for (QueryDocumentSnapshot<Map<String, dynamic>> document
          in querySnapshot.docs) {
        Map<String, dynamic> chatSettings = document.data();
        if (chatSettings['active'] == true) {
          await firestore
              .collection('chat_rooms')
              .doc(document.id)
              .collection("messages")
              .orderBy('timestamp', descending: true)
              .limit(1)
              .get()
              .then((messagesSnapshot) {
            Map<String, dynamic> lastMessage =
                messagesSnapshot.docs.first.data();
            if (lastMessage['read'] == false &&
                lastMessage['receiver_id'] == userId) {
              unreadCount++;
            }
          });
          
        }
      }
      return unreadCount;
    });
  }
 

Suggestion

By the way, this is not correct way to count unread messages as this is costly and time consuming (because get docs one by one) because in order to get a number you fetched so many document.

You can use collectionGroup() and count().

Read this (CollectionGroupQuery but limit search to subcollections under a particular document) and this (Count documents with aggregation queries).

Rajendra A Verma
  • 268
  • 2
  • 11