import 'dart:async'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:logging/logging.dart'; /* * PaginationModel load data in page * and listen to document change based on 'update_time' and 'delete_time' fields * of the document */ class PaginationModel { final log = Logger('PaginationModel'); List ids = []; DocumentSnapshot prev; int rowPerLoad = 10; bool ended = false; StreamSubscription listener; CollectionReference listeningCol; Query pageQuery; PaginationModel(CollectionReference listeningCol, Query pageQuery, {this.rowPerLoad = 10}) { this.listeningCol = listeningCol; this.pageQuery = pageQuery; initData(); } void initData() async { _clearState(); _initListener(); load(); } void _clearState() { prev = null; ids = []; ended = false; if (listener != null) listener.cancel(); listener = null; if (controller != null) controller.close(); } StreamController controller; Stream listen() { if (controller != null) { controller.close(); } controller = StreamController(onCancel: _clearState); return controller.stream; } void close() { _clearState(); } final String updateTimeField = 'update_time'; final String deleteTimeField = 'delete_time'; void _initListener() { Query _query = listeningCol.orderBy(updateTimeField, descending: true).limit(1); _query.getDocuments(source: Source.server).then((QuerySnapshot snapshot) { int count = snapshot.documents.length; int updateTime = 0; if (count == 1) { updateTime = snapshot.documents[0].data[updateTimeField]; } Query _queryListener = listeningCol .where(updateTimeField, isGreaterThan: updateTime) .orderBy(updateTimeField, descending: true); listener = _queryListener.snapshots(includeMetadataChanges: true).listen((qs) { qs.documentChanges.forEach((c) { switch (c.type) { case DocumentChangeType.added: log.info("added!! $c"); _update(c.document.documentID, c.document.data); break; case DocumentChangeType.modified: log.info("modified!! $c"); _update(c.document.documentID, c.document.data); break; default: } }); }); }); } void _update(String id, Map data) { if (ids.contains(id)) { var deleted = data[deleteTimeField]; if (deleted > 0) { ids.remove(id); controller.add(Result( id: id, data: data, documentChangeType: DocumentChangeType.removed)); } else { controller.add(Result( id: id, data: data, documentChangeType: DocumentChangeType.modified)); } } else { ids.add(id); controller.add(Result( id: id, data: data, documentChangeType: DocumentChangeType.added)); } } Future load() async { Query _query = prev != null ? pageQuery.startAfterDocument(prev) : pageQuery; try { await _query .where(deleteTimeField, isEqualTo: 0) .limit(rowPerLoad) .getDocuments(source: Source.server) .then((QuerySnapshot snapshot) { int count = snapshot.documents.length; ended = count < rowPerLoad; prev = count > 0 ? snapshot.documents[count - 1] : prev; snapshot.documents.forEach((e) { if (!ids.contains(e.documentID)) log.shout("load!! $e"); ids.add(e.documentID); controller.add(Result( id: e.documentID, data: e.data, documentChangeType: DocumentChangeType.added)); }); if (ended) { controller.add(Result(isEnded: true)); } }); } catch (e) { log.warning("Error!! $e"); } return ended; } } class Result { String id; Map data; DocumentChangeType documentChangeType; bool isEnded; Result({this.id, this.data, this.documentChangeType, this.isEnded = false}); }