'Flutter Firestore real-time pagination using multiple listeners streamed combined
I want to create a pagination screen, using Bloc, from the Firestore database. The screen should update on document changes.
My FirestoreProviderApi
receives fetch requests from the Bloc unit. After fetching the documents from Firestore, it sends them back to the Bloc using a Stream
.
The FirestoreProviderApi
:
final _pollOverviewStreamController = BehaviorSubject<QuerySnapshot>();
@override
Stream<QuerySnapshot> getOverviewPolls<QuerySnapshot>() =>
_pollOverviewStreamController.asBroadcastStream().cast();
@override
Future<void> fetchFirstOverviewPolls() async {
_overviewPollsRef.limit(5).snapshots().listen((querySnapshot) {
_pollOverviewStreamController.add(querySnapshot);
});
}
@override
Future<void> fetchNextOverviewPolls() async {
if (_pollOverviewStreamController.value.docs.isEmpty) return;
final lastDoc = _pollOverviewStreamController.value.docs.last;
_overviewPollsRef
.startAfterDocument(lastDoc)
.limit(5)
.snapshots()
.listen((querySnapshot) {
_pollOverviewStreamController.add(querySnapshot);
});
}
On document changes, I want to update my list of documents.
Right now, whenever a change occurs, a new QuerySnapshot is appended to the Stream (instead of replacing the old one). Is there a way to combine multiple listeners to the same Stream, aggregating only the most up-to-date data?
Solution 1:[1]
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).
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |