Firestore Querying a Snapshot within a Snapshot?

Viewed 111

I am trying to listen for any notifications whenever someone has replied to a post that a user has commented on. Below is how my database structure looks.

  • Posts: (Collection)
    • Post 1: (Document)
      • replies: [user2, user3]
        • Replies: (Collection)
          • Reply 1: (Document)
            • ownerId: [user2]
          • Reply 2: (Document)
            • ownerId: [user3]

Currently my code has 2 snapshot listeners. The first one listens to the Posts collections, where a user is inside the 'replies' array. Then the second one listens to the Replies collection, where it returns all documents added that != the current user. When a new reply has been detected, it will set the Tab Bar item's badge.

This currently works right now, but I am curious if there is a better method of doing so.

func getNotifications() {
        database.collection("Posts")
            .whereField("replies", arrayContains: userData["userId"]!)
            .order(by: "timestamp", descending: true)
            .limit(to: 70)
            .addSnapshotListener() { (querySnapshot, err) in
                if let err = err {
                    print("Error getting documents: \(err)")
                }
                else {
                    guard let snapshot = querySnapshot else {
                        print("Error fetching snapshots: \(err!)")
                        return
                    }
                    snapshot.documentChanges.forEach { documentd in
                        if (documentd.type == .added) {
                            let dataTemp = documentd.document.data()
                            let ifUser = dataTemp["ownerId"] as! String

                            if(ifUser == self.userData["userId"]!) {
                                database.collection("Posts")
                                    .document(documentd.document.documentID)
                                    .collection("Replies")
                                    .whereField("timestamp", isGreaterThan: dataTemp["timestamp"] as! Int)
                                    .addSnapshotListener() { (querySnapshot3, err) in
                                        if let err = err {
                                            print("Error getting documents: \(err)")
                                        }
                                        else {
                                            guard let snapshot = querySnapshot3 else {
                                                print("Error fetching snapshots: \(err!)")
                                                return
                                            }
                                            snapshot.documentChanges.forEach { diff in
                                                if (diff.type == .added) {
                                                    let temp = diff.document.data()
                                                    if((temp["ownerId"] as! String) != self.userData["userId"]!) {
                                                        print("new reply")
                                                        newArr.append(diff.document.data())
                                                        let data = diff.document.data()
                                                        let firebaseTime = data["timestamp"] as! Int
                                                        let date = lround(Date().timeIntervalSince1970)
                                                        if(firebaseTime+10 > date) {
                                                            self.tabBar.items![2].badgeValue = "●"
                                                            self.tabBar.items![2].badgeColor = .clear
                                                            self.tabBar.items![2].setBadgeTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.red], for:.normal)
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                            }
                            else {
                                database.collection("Posts")
                                    .document(documentd.document.documentID)
                                    .collection("Replies")
                                    .whereField("ownerId", isEqualTo: self.userData["userId"]!)
                                    .order(by: "timestamp", descending: false)
                                    .limit(to: 1)
                                    .getDocuments() { (querySnapshot2, err) in
                                        if let err = err {
                                            print("Error getting documents: \(err)")
                                        }
                                        else {
                                        var timestamp = Int()
                                        for documentde in querySnapshot2!.documents {
                                                 
                                            let temp = documentde.data()
                                            timestamp = temp["timestamp"] as! Int
                                        
                                            
                                            database.collection("Posts")
                                                .document(documentd.document.documentID)
                                                .collection("Replies")
                                                .whereField("timestamp", isGreaterThan: timestamp)
                                                .addSnapshotListener() { (querySnapshot3, err) in
                                                    if let err = err {
                                                        print("Error getting documents: \(err)")
                                                    }
                                                    else {
                                                        guard let snapshot = querySnapshot3 else {
                                                            print("Error fetching snapshots: \(err!)")
                                                            return
                                                        }
                                                        snapshot.documentChanges.forEach { diff in
                                                            if (diff.type == .added) {
                                                                let temp = diff.document.data()
                                                                if((temp["ownerId"] as! String) != self.userData["userId"]!) {
                                                                    print("new reply")
                                                                    newArr.append(diff.document.data())
                                                                    let data = diff.document.data()
                                                                    let firebaseTime = data["timestamp"] as! Int
                                                                    let date = lround(Date().timeIntervalSince1970)
                                                                    if(firebaseTime+10 > date) {
                                                                        self.tabBar.items![2].badgeValue = "●"
                                                                        self.tabBar.items![2].badgeColor = .clear
                                                                        self.tabBar.items![2].setBadgeTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.red], for:.normal)
                                                                    }
                                                                }
                                                            }
                                                        }
                                                    }
                                                }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

    }
1 Answers

Your code does not have only two listeners, but one listener per post for which the user you are interested in has ever replied for. This will lead to terrible performances very soon and will potentially crash you app as Firestore has a limitation of 100 listeners per client.

I would advise to redesign your data model:

  1. Only one listener on posts that the user has ever replied to (your first listener)
  2. On each reply increment a reply counter in the post doc, this will trigger the snapshot above.
  3. Optimisation 1: on each action on a post you could set a field lastactiontype, which would have a specific value reply for replies only. This way the snapshot is only triggered on replies.
  4. Optimisation 2: set a field timestamp on each action to the current time and only pull the last n (for instance 10) posts in your snapshots, this will limit the number of reads at load time. You will have to implement some special logic to handle the case when your app goes offline and back online and all n posts of the snapshot have changed. This is a must if your app is meant to scale (you dont want a snapshot with no limit on a collection with 100k docs...)

Example:

firestore.collection("Posts")
.where( "lastaction", "==" , "reply")
.where( "replies", "array-contains", uid)
.orderBy("timestamp", "desc").limit(10)
Related