Firestore Paginating Data + Snapshot Listener

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:

  1. Create a class-scoped document list state variable.
  2. Stream each time the whole list.
  3. Listen and iterate over snapshot changes, and update/add documents to the main list.
  4. 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



Leave a reply



Submit