Riverpod: stop watching Firestore collection while screen is not visible - flutter

I am writing a cross-platform app in Flutter, with Riverpod for managing state, and Firestore as the back-end database. This question explores one of the alternatives I proposed in this other question.
I'm using Riverpod's StreamProvider to subscribe to a certain Firestore collection, and a provider family to listen to individual documents within that collection.
final collectionProvider = StreamProvider.autoDispose((ref) => Firestore.instance.collection("my_collection").snapshots());
final documentProviderFamily =
StreamProvider.autoDispose.family<DocumentSnapshot, String>((ref, documentId)
=> Firestore.instance.collection("my_collection").doc(documentId).snapshots());
I have a page (a widget) that listens to the collection and shows some info about each document in a ListView:
CollectionPage extends ConsumerWidget {
...
void build(BuildContext context, WidgetRef ref) {
final snapshot = ref.watch(collectionProvider);
...
}
}
If the user clicks on a ListTile, a details page (a widget) is opened, with Navigator.of(context).push(...), showing detailed info about the corresponding document.
Importantly, this details page also needs to update in real-time, so just passing it the current object/document snapshot held by the collection page doesn't work. Instead, this widget watches that individual document from Firestore:
DocumentDetailsPage extends ConsumerWidget {
...
final String documentId;
void build(BuildContext context, WidgetRef ref) {
final snapshot = ref.watch(documentProviderFamily(documentId));
...
}
}
Suppose we have one of these details page open. Since the navigator stack still contains the CollectionPage widget, the subscription to the collection stream is still active. So, whenever the document in question is updated, two reads will occur on Firestore: one for the collection subscription and another one for the single document subscription. (I found this out by using the Firestore emulator and looking at the requests tab.)
Since the CollectionPage is not visible, the former update is wasteful. A single update to the document is causing duplicate reads of the same document (being charged accordingly in Firestore). Ideally I would like to cancel the subscription to the entire collection when the details page is opened, and re-start it when the details page is popped.
In riverpod terms: how do I temporarily un-watch the provider while the widget is not visible and start watching again when it becomes visible again?
I've read Dispose widget when navigating to new route, and it seems this kind of behaviour (cancelling the listener when the screen is not visible) should go into the deactivate() method of the widget, but then again I don't know how to make Riverpod do this, and the Q&A mentions that this method is not reliable anyways.

Related

riverpod provider called once - why are subsequent calls to the provider "cached?"

I'm using Riverpod and I have a relatively simple provider that I'm using to get the number of pending writes from Firestore. This is basically used to help give users feedback as to whether or not their data is fully synced.
final pendingUploadsCounterProvider =
FutureProvider.autoDispose.family<int, List<Report>>((ref, reports) async {
final db = ref.read(firebaseServiceProvider).db;
final reportIds = reports.map((e) => e.id).toList();
final documents = await Future.wait([
for (final name in kReportTypes)
db
.collection(name)
.where('report_id', whereIn: reportIds)
.get(GetOptions(source: Source.cache))
]);
return documents.where((event) => event.metadata.hasPendingWrites).length;
});
As you can see, all I'm doing is reading some data from the Firestore cache and then filtering that list based on the hasPendingWrites property.
I'm attempting to use this provider on multiple screens as I need to provide the user feedback on their sync status in multiple locations.
When I use this provider on a single screen only, every time I enter the screen, the provider is triggered and I get an up to date status.
When I use this provider on multiple screens, the first time I enter a screen where it is used, the provider is triggered. Subsequently, entering any other screen where this provider is used, the provider is no longer triggered and therefore, the results are out of date.
I thought by using autoDispose, the provider would be disposed of when I leave the screen, but that doesn't appear to be the case.
I'm sure that there's a disconnect with my understanding of riverpod and how this works and hopeful someone can shed some light on my misunderstanding.
Thank you!
the provider will not be disposed while the widget in the tree you need to remove it from the back stack to be disposed
in your case, you need to reload it on other screens
by calling
context.refresh(pendingUploadsCounterProvider);

Query Regarding Stream Provider in Flutter

I'm currently trying out Stream Providers for noticing changes in Cloud Firestore Database and getting new data on change.
This is how I have created the Stream Provider :
child: StreamProvider.value(value: _firestoreAPI.getProjectsFromStream(_savedUser.user.name),child: PrimaryScene(toggleAnimation)),
As I am not able to call the provider from initstate ....Hence I'm calling is inside build :
_projectListSnapshotFromStream = Provider.of<QuerySnapshot>(context);
I have a doubt that if I rebuild the Widget containing the above call in its build function, will it charge me a read everytime I build that Widget because I called the provider? Or will it only charge a read when there is a change noticed in the database ?
Stream Definition :
Stream<QuerySnapshot> getProjectsFromStream(String username){
return databaseReference.collection("Projects").where("Members",arrayContains: username).snapshots();
}
Thankyou in advance for answering.
will only count as read if you receive a new document from firestore

Dart: Get current value in Stream

I am making a Flutter App, where I use Firebase Auth and Cloud Firestore. For every user, I have a trainer document with the user uid as the document id.
When I update the data of my trainers, I want to make sure that only the document of the currently logged in user can be modified.
I have an AuthService class with the following method to get the current user:
Stream<FirebaseUser> get user {
return _auth.onAuthStateChanged;
}
So when I want to change the trainer document in my DatabaseService class, I want to get the current value of the stream to get the user uid. I do not think that I can use StreamBuilder or Provider, as this is not a Widget Tree, but a simple Dart class. However, I do not think that I can use .listen() either, as I only want to get current Stream value, not all of them.
How do I get the current value of a Dart Stream?
You could probably keep a local variable currentUser, listen to the Stream, and update currentUser everytime the Stream gives a new value. Read currentUser, whenever you want to get the current user.

How to get progression of getDocuments firestore

I am retrieving a lot of documents from Firestore, it can take a lot of time depending on the network connection of the user.
I would like to display a progres bar.
I didn't find any documentation on this
Here is my code :
final databaseReference = Firestore.instance;
databaseReference.collection("XXX").getDocuments().then((QuerySnapshot snapshot) {
Map<String, Transac.Transaction> map = new Map<String, Transac.Transaction>();
snapshot.documents.forEach((f) {
//doing something
});
I would like to have a percentage of the data loaded. For example :
Loading (58%)
Thank you.
Firestore doesn't expose any progress indicator for the documents within a query. By the time your then callback gets called, all documents have been loaded on the client and are present in the QuerySnapshot.
The usual way to deal with this is to show a "spinner" instead of a progress bar, and then hide that spinner as the first code inside your then callback.
What you can do instead is just call getDocument() for each document individually, and track their individual completions. You should track them in the order that you issued them, as the requests are pipelined over a single connection. This would give you a rough sense of progress, if you're requesting lot of documents.

How to get all documents from a collection without using StreamBuilder?

I want to get all documents from firestore collection without using stream builder, otherwise I will have nested streams and in the last stream I am getting a null. Initial data is not solving my problem.
Now I make a stream builder for that collection, when a snapshot is chosen I jump on other widget and there I also make a stream builder and here is the problem.
I want to make stream builder only in the last widget of the scenario, because only there realtime data update is needed.
You can use FutureBuilder with the following method
await Firestore.instance.collection("books").getDocuments()
Get Realtime data using this:
Firestore.instance
.collection('books').snapshots().listen((querySnapshot){
});