Reputation: 172
When fetching all documents of a collection from Firestore
using the get()
method, by default it won't throw an exception if there is no internet connection. Instead it looks up the cache and returns cached data. But sometimes you need to know whether a collection is actually currently empty or you just cannot connect to the database.
And for just fetching data there is a solution to that: the GetOptions
parameter can be set to Source.server
. That way an error will be thrown if the server cannot be reached.
Now I'm looking for an equivalent of this option for the snapshots()
method that returns a Stream
instead of just single data. So I need the stream to emit an error when it cannot connect to the server.
So:
await _firestore
.collection('stuff')
.where("someBool", isEqualTo: false)
.get(const GetOptions(source: Source.server));
will throw a FirebaseException
on bad connection, while:
_firestore
.collection('stuff')
.where("someBool", isEqualTo: false)
.snapshots()
.listen((snap) { ... }, onError: (e) {
//never called...
debugPrint(e.toString());
});
in this example I just can't find out.
Closest to a solution I got was checking on snap.metadata.isFromCache
and throwing an exception when the value is true
.
But this does not work reliably. False meaning the snapshot is guaranteed to be up to date with the server. But in some cases it is true
even though it got a valid response from the server.
Edit: isFromCache
property would be a solution if I filtered someBool
locally instead of directly in the query.
_firestore
.collection('stuff')
.where("someBool", isEqualTo: false)
.snapshots(includeMetadataChanges: true)
.listen((snap) {
//`hasPendingWrites` is false in all these cases:
// 1. changing a document's `someBool` field from `false` to `true` and vise versa
// 2. requesting with server connection
// 3. requesting without server connection
debugPrint("snap metadata pendingWrites is: ${snap.metadata.hasPendingWrites}");
if (snap.metadata.isFromCache) {
//I thought data from cache is only used when there's trouble connecting to the server which is not the case:
//
//no internet connection will indeed cause this value to be true
//but changing a document's `someBool` field from `false` to `true` however, will cause it to be true, too
debugPrint("metadata of this snap is from cache -> throwing NoInternetException");
throw NoInternetException();
}
}, onError: (e) {
//never called...
debugPrint(e.toString());
});
Edit: I have offline persistence disabled:
db.settings = const Settings(persistenceEnabled: false);
Experiment: Updating a document's someBool
field (directly from Firestore Console
):
with internet:
true to false: pendingWrites
is false
, isFromCache
is false
false to true: pendingWrites
is false
, isFromCache
is true
When just turning the internet off while the Stream
is being listened to:
pendingWrites
is false
, isFromCache
is true
So the 'false to true' case with internet leads to a false positive using the isFromCache
member.
Edit:
The documentation states:
If you called [DocumentReference.snapshots] or [Query.snapshots] with
includeMetadataChanges
parameter set totrue
you will receive another snapshot withisFomCache
equal tofalse
once the client has received up-to-date data from the backend.
Now in this false positive case (setting the bool from false
to true
) the Stream
fires twice:
isFromCache
: true
isFromCache
: false
So for some reason Firestore reads from the cache even though there is a connection to the server. So I can't rely on this property when it comes to throwing NoInternetException
.
Upvotes: 5
Views: 2057
Reputation: 177
See my ticket i opened in June: https://github.com/firebase/flutterfire/issues/8961
As you can see in the last entry i've opened a feature request at Firebase support which asks for adding the GetOptions parameter in Query.snapshots and DocReference.snaphots calls.
Also you could have a look at the Nimbostratus package which tries to overcome the current limitations in Firestore.
Upvotes: 1
Reputation: 4806
The behavior you're describing is exactly what Firestore developers wanted, and indeed it has its drawbacks..
The only thing I'd add to your current solution would be a connectivity check using either something like connectivity_plus or internet_connection_checker
final _hasConnection = await yourConnectivityProvider.isConnected();
_firestore
.collection('stuff')
.where("someBool", isEqualTo: false)
.snapshots(includeMetadataChanges: true)
.listen((snap) {
//`hasPendingWrites` is false in all these cases:
// 1. changing a document's `someBool` field from `false` to `true` and vise versa
// 2. requesting with server connection
// 3. requesting without server connection
debugPrint("snap metadata pendingWrites is: ${snap.metadata.hasPendingWrites}");
if (snap.metadata.isFromCache) {
//I thought data from cache is only used when there's trouble connecting to the server which is not the case:
//
//no internet connection will indeed cause this value to be true
//but changing a document's `someBool` field from `false` to `true` however, will cause it to be true, too
if (!_hasConnection) {
debugPrint("metadata of this snap is from cache -> throwing NoInternetException");
throw NoInternetException();
}
}
}, onError: (e) {
//never called...
debugPrint(e.toString());
});
connectivity_plus will only detect if the device has it's Wifi/4G ON, but it's pretty light and won't affect the battery.
internet_connection_checker will give you a 'real' info about the connectivity, but it has to query a server to get this info.
Will it work 100% of the time ? Probably not.
There are a few use-cases where, even with this 'security', you may get false positives (or even false-negatives, which is even worst).
Upvotes: 1
Reputation: 97
In order to do what you want you have to: First make sure that cache is disabled and cleared:
FirebaseFirestore.instance.settings =
Settings(persistenceEnabled: false);
await FirebaseFirestore.instance.clearPersistence();
Now, when you try to access anything without network connection, you should get an empty snapshot with "isFromCache" equal to true. Unfortunately there is no error in this case. It is just the way it is designed.
Hope I helped.
Upvotes: 2
Reputation: 5973
Firebase applications work even if your app temporarily loses its network connection. In addition, Firebase provides tools for persisting data locally, managing presence, and handling latency.
like as example you can check the internet connection like that, Detecting Connection State
final connectedRef = FirebaseDatabase.instance.ref(".info/connected");
connectedRef.onValue.listen((event) {
final connected = event.snapshot.value as bool? ?? false;
if (connected) {
debugPrint("Connected.");
} else {
debugPrint("Not connected.");
}
});
and also for more details, you can check out this link:- https://firebase.google.com/docs/database/flutter/offline-capabilities
and for particularly firestore you can follow this,
db
.collection('stuff')
.where("someBool", isEqualTo: false)
.snapshots(includeMetadataChanges: true)
.listen((querySnapshot) {
for (var change in querySnapshot.docChanges) {
if (change.type == DocumentChangeType.added) {
final source =
(querySnapshot.metadata.isFromCache) ? "local cache" : "server";
print("Data fetched from $source}");
}
}
});
for more detail follow this link(for firestore):- https://firebase.google.com/docs/firestore/manage-data/enable-offline#dart_2
Upvotes: 4