Firestore Paginating data + Snapshot listener
Well, I contacted the guys over at Firebase Google Group for help, and they were able to tell me that my use case is not yet supported.
Thanks to Kato Richardson for attending to my problem!
For anyone interested in the details, see this thread
Flutter Firestore real-time pagination using multiple listeners streamed combined
Issue solved.
I found good documentation of how to implement real-time pagination using Firestore (been implemented it, works like a charm).
Youtube implementation: Flutter and Firestore real-time Pagination.
High-level steps:
- Create a class-scoped document list state variable.
- Stream each time the whole list.
- Listen and iterate over snapshot changes, and update/add documents to the main list.
- If the list is not empty, then fetch from the last document on the list.
My implementation:
final List<DocumentSnapshot> _overviewPolls = [];
@override
Future<void> fetchOverviewPolls(
[int firstFetchLimit = 1, int nextFetchLimit = 1]) async {
Query overviewPollsQuery = _overviewPollsRef.limit(firstFetchLimit);
if (_overviewPolls.isNotEmpty) {
overviewPollsQuery = overviewPollsQuery
.startAfterDocument(_overviewPolls.last)
.limit(nextFetchLimit);
}
overviewPollsQuery.snapshots().listen((querySnapshot) {
if (querySnapshot.docs.isNotEmpty) {
for (final doc in querySnapshot.docs) {
int index = _overviewPolls.indexWhere((d) => d.id == doc.id);
// if already exists - update poll
if (index >= 0) {
_overviewPolls[index] = doc;
}
// if new document - add poll
else {
_overviewPolls.add(doc);
}
}
_pollOverviewStreamController.add(_overviewPolls);
}
});
}
Using orderBy
Note that if you are using orderBy
on querying your snapshot, any update to the order's field will cause a reordering of the whole list of items. Moreover, in some cases, it will fetch automatically more items (since we are using a limit for each snapshot, and the list of items is reordered, some snapshots may need to fetch more items to fill its limitation).
Get older snapshot for pagination when querying with limit(toLast:)
Say you have 10 documents:
[1,2,3,4,5,6,7,8,9,10]
Your limitToLast(5)
will retrieve:
[6,7,8,9,10]
Since you're using start(afterDocument:
, Firestore starts after document 6
, which is not what you want. Instead you want to to end(beforeDocument:
:
query = DBCall.fireRef.collection("friendMessages")
.document(currentUID)
.collection(friendUID)
.order(by: "timestamp")
.end(beforeDocument: nextStartingSnap)
.limit(toLast: 5)
I also reverse the limit(toLast:
and end(beforeDocument:
. It makes no difference to the results, but reads slightly easier to me - as it's pretty much how Firestore processes your query:
- It loads the index on
timestamp
. - It cut off everything from
nextStartingSnap
onwards. - It then returns the last 5 documents that remain in the result.
Paginating firestore data with realtime additions on top of the result
I think you should save state of last document for pagination and realtime updates
Example
const getPalettes = (pageSize, lastDocument) => new Promise((resolve, reject) => {
let query = db.collection('palettes')
.orderBy("createdAt")
if(lastDocument) {
query = query.startAt(lastDocument)
}
query = query.limit(pageSize);
return query.onSnapshot(query => {
const docs = query.docs.map(pr => ({pr.id, ...pr.data()}))
resolve(docs);
});
})
let unsubscribe = getPalettes(10).then(newPalettes => {
setPalette(palettes => [...palettes, newPalettes]);
lastPalette = newPalettes[newPalettes.length -1];
setLastPalette(lastPalette);
unsubscribe();
})
unsubscribe = getPalettes(10, lastPalette).then(newPalettes => {
setPalette(palettes => [...palettes, newPalettes]);
lastPalette = newPalettes[newPalettes.length -1];
setLastPalette(lastPalette);
unsubscribe();
})
const listenForLatestPalettes = (lastDocument, callback) => {
return db.collection('palettes')
.orderBy("createdAt")
.startAt(lastDocument)
.onSnapshot(callback);
}
const callback = snapshot => {
for(let change of snapshot.docChanges()) {
if (change.type === "added") {
setPalette(palettes => {
const palette = { id: change.doc.id, ...change.doc.data() };
return [...palettes.filter(pal => pal.id !== id], palette];
})
}
}
}
unsubscribe = listenForLatestPalettes(lastDocument, callback);
How do I remove the listener for addSnapshotListener used in Firestore pagination?
When you call addSnapshotListener
it returns a function that you can call to remove the listener. So you need to store the return value in a variable, and then call remove
on it after that.
From the documentation on detaching a listener:
let listener = db.collection("cities").addSnapshotListener { querySnapshot, error in
// ...
}
// ...
// Stop listening to changes
listener.remove()
Related Topics
Application Identifier Entitlement Value Has Changed
Get Cellid, Mcc, Mnc, Lac, Signal Strength, Quality and Network in iOS 8.3
Send Image Along with Other Parameters with Afnetworking
Simple Uitableview in Swift - Unexpectedly Found Nil
Uitapgesturerecognizer on Uiimageview Within Uitablevlewcell Not Getting Called
Passing Data Between Two Viewcontrollers (Delegate) - Swift
Xcode 7 Crash: [Nslocalizablestring Length] 30000
How to Make Alamofire Download Progress Run in Background iOS
Google Maps Sdk Not Displaying Properly in Uiview
Sending Latitude and Longitude to Server When App Is in Background
How to Determine If a User Has an iOS App Installed
Getting Random "Facebookerrdomain Error 10000"
Uipickerview as Inputview of Uitextfield
Gamecenter iOS 9 Gamecenter Gklocalplayerlistener Methods Not Called
iOS Nsnotificationcenter to Check Whether the App Came from Background to Foreground