I am currently dealing with pagination with addSnapshotListener in Firebase's firestore and it appears there's no easy way to implement Snapshot with pagination.
Original premise: I started the implementation with addSnapshotListener as follows:
db.collectionGroup("images")
.order(by: "createdTime")
.whereField("featured", isEqualTo: true)
.addSnapshotListener { querySnapshot, _ in
if let querySnapshot = querySnapshot {
vm.featuredImages = querySnapshot.documents.compactMap { document in
do {
let image = try document.data(as: ImageModel.self)
print("DEBUG FEATURED IMAGE: \(String(describing: image))")
return image
} catch {
print("DEBUG FEATURED IMAGE ERROR \(String(describing: error))")
}
return nil
}
}
}
And all goes well. The data is fetched into the ViewModel and any new changes are automatically notified via Firestore’s library and the local model gets updated.
Now add pagination: I’ve read the official documentation as well as all the stackoverflow threads. It appears there is no easy to maintain a addSnapshotListener with a new page.
(A) One naive approach when a new page is requested would be to
This seems to work ok on the surface however with the one big problem is that you would be re-fetching the first 10 when you request for page 2. And the fetches become exponential as you add pages!
(B) Another solution mentioned in Firebase’s official youtube is
I imagine querySnapshot is the standard way of keep data in sync with apps. I imagine every app is going to need pagination. What is the correct solution?
A. First part is to handle new documents and display document for the first time during the pagination:
get() query, separate from the listenerThe difficult part here is to handle the case when your app goes offline
B. Secondly, you have to handle updates on documents that are not part of the snapshot listener (all the one after the first 10).
get query to the backend every time a new such document is scrolled to, checking if it has been upddated since the last time it was loaded. I have optimized this get query as follows:whenever a document is updated (including deletions) a field time_updated is set
when a document is fetched my app stores locally the document's time_updated as last_pulled_updated
when a user scrolls back to a document my app will fetch it as well as the next 9 with the following query:
.order(by: "time_updated", descending: true)
.whereField("time_updated", isGreaterThan: min_last_pulled_updated) // Minimum of last_pulled_updated over the 10 docs
.whereField("featured", isEqualTo: true)
.whereField("docid", in: [docid1,docid2,docid3,docid4,docid5,docid6,docid7,docid8,docid9,docid10])
this costs at most 10 reads and may cost only 1 read if one or no document is returned
C. Deletions. By deletion I mean rendered innaccessible to the end user, not deleted from Firestore. So a "deleted" document could simply have a field deleted set to true and the listener and pagination query will both contain.whereField("deleted", isEqualTo: false). You can use TTL to have it really deleted later on.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With