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
includeMetadataChangesparameter set totrueyou will receive another snapshot withisFomCacheequal tofalseonce 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:
- First time with
isFromCache:true - Second time (immediately after first time):
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.