Update Firestore database just before internet disconnect - flutter

Future<void> setActiveDataToApi(
String referencePath1, String referencePath2, bool activeState) async {
await _firestore
.collection(referencePath1)
.doc(referencePath2)
.update({"active": activeState});
I want update user active status just before internet disconnect, how can I do that?
This one is update data my database, I'll update timestamp too but if I periodically update data like 10 second it cost me too much, OnDisconnect()/onDisconnectSetValue() function not exist for firestore and I think it's not for internet just for close app. I use firebase/cloud firestore

u can use the AppLifeCycle to achieve this!
https://api.flutter.dev/flutter/widgets/WidgetsBindingObserver-class.html
class _CustomState
extends State<WidgetBindingsObserverSample> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if(state==AppLifecycleState.paused){
_syncStatus(status: <Update DB value to offline> );
return;
}
if(state== AppLifecycleState.resumed){
_syncStatus(status: <Update DB value to online> );
return;
}
}
}
Note: this is just an example this can be further optimised and improved
If u want to actually update status whether the user is connected to network or not u can use connectivity_plus, the process for this can be diff, u will have to listen for any network changes for the api provided in the package and call ur api depending on which connection status u wanted to update

There is no reliable way to detect when the internet connection is about to disappear. Imagine being in a train that drives into the tunnel. The first time the app can know that there's no connection is when that connection is already gone, and by then it's too late to write to the database.
Firebase Realtime Database's onDisconnect handler, send a write operation to the server when they are connected, that the server then executes when it detects that the client is gone. There is no equivalent in Firestore, because the wire protocol Firebase uses is not suited for it.
Since you indicate not wanting to do a periodic update, the easiest way I can think of to do this is to actually use both databases in your app, and then use Cloud Functions to carry an onDisconnect write operation from the Realtime Database to Firestore. This is in fact exactly the approach that is outlined in the documentation solution on building a presence system on Firestore, so I recommend checking that out.

Related

Sometimes my Cloud Function returns old data from Firestore. Is it a cache problem?

Client-side, I'm using a listener to detect if the "notifications" collection of the user changes. The App calls a Cloud Function that retrieves the last three unread notifications and the total number of unread notifications.
In my App, I have this:
Listener
firestore.collection("users")
.doc(uid)
.collection("notifications")
.snapshots().listen((QuerySnapshot querySnapshot) {
NotificationsPreviewModel notificationsPreview =
await _cloudFunctionsService.getNotificationsPreview(doctor.id)
})
Cloud Function
exports.getNotificationsPreview = functions.https.onCall(async (data, context) => {
const userId = data.userId;
let notifications = [];
const notificationsDocuments = await db
.collection("users")
.doc(userId)
.collection("notifications")
.orderBy("dateTime", "desc")
.get();
notifications = notificationsDocuments.docs.map((rawNotification) =>
rawNotification.data()).filter((element) => element.unread == true);
const notificationsNumber = notifications.length;
notifications = notifications.slice(0, 3);
return { "notifications": notifications, "notificationsNumber": notificationsNumber };
});
The Cloud Function gets called only when a change is detected, so it shouldn't return old data.
The error appears only the first time the Cloud Function is called from the App's start, but not always. The following calls don't generate the error.
How can I solve this? For now, I've added a delay of 500ms, and it works perfectly, but it's not a real solution.
Based on your description, it sounds like you see some form of latency while collecting the data from Firestore. Retrieving data from the Cloud takes time, and a delay of 500ms is not excessive.
I am not familiar with Flutter enough to comment on your code. However, according to the documentation for Java:
By default, get() attempts to provide up-to-date data when possible by waiting for data from the server, but it may return cached data or fail if you are offline and the server cannot be reached. This behavior can be altered via the Source parameter.
Source:
By providing a Source value, these methods can be configured to fetch results only from the server, only from the local cache, or attempt to fetch results from the server and fall back to the cache (which is the default).
If you are online, get() checks the server for the latest data, which can take between 300ms and 1500ms depending on several factors. For example, where is your Firestore instance located in comparison to your Cloud Function and client? Try adjusting the delay and see if you can identify the timing.
There are also some soft limits you should be aware of as this might also impact your timings for how quickly you can retrieve the data. There is a maximum sustained write rate to a document of 1 per second. Sustaining a write rate above once per second increases latency and causes contention errors.
As for the documentation:
When you set a listener, Cloud Firestore sends your listener an initial snapshot of the data, and then another snapshot each time the document changes.
It seems that you are initially receiving the snapshot of the data, and then the following updates, as expected.
You can check some possible solutions to this in this post.

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);

Firebase/cloud firestore: onSnapshot() vs on()

I have been using onSnapshot successfully to alert my code to changes in underlying data, as in
// Set up to listen for changes to the "figures" collection, that is,
// someone has created a new figure that we will want to list on the screen.
setFiguresListener: function () {
// `figuresCR` is a collection reference defined elsewhere
return this.figuresCR.onSnapshot((iFigs) => {
iFigs.forEach((fSnap) => {
const aFigure = figureConverter.fromFirestore(fSnap, null);
const dbid = aFigure.guts.dbid; // ID of the "figure" in the database
nos2.theFigures[dbid] = aFigure; // update the local copy of the data
});
nos2.ui.update();
console.log(` Listener gets ${iFigs.size} figures`);
});
But I now read about on in the docs. It explains:
[The on() function] Listens for data changes at a particular location.
This is the primary way to read data from a Database. Your callback
will be triggered for the initial data and again whenever the data
changes. Use off( )to stop receiving updates. See Retrieve Data on
the Web for more details.
The syntax is a bit different, and on() seems to do much the same as onSnapshot().
So what is the real difference? Should we be using on() instead of onSnapshot()?
on() is an operation for reading from Firebase Realtime Database. That's a completely different database with different APIs than Firestore. They have essentially no overlap. There is no on() operation with Firestore.
If you're working with Firestore, ignore all the documentation about Realtime Database, and stick to using onSnapshot() for getting realtime updates.
Other tyros who fall into this tar pit: in the API doc pages, you might think that since firestore is a database under firebase, you could look for help under firebase.database. But no: look only in the next section, firebase.firestore.

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 does the Cloud Firestore Batched writes work in offline mode

In the doc about Cloud Firestore Batched writes I read "You can perform batched writes even when the user's device is offline."
Can this be turned off or is it off by default since the text say "can". The Cloud Firestore Transaction will fail if it´s offline and I would like Batched writes to do the same
I get the feeling I have to check for the result in the OnCompleteListener:
batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
// offline/online??
}
});
I think the Doc should contain some explanation on this
Firestore transactions exist to make it possible to perform atomic read-modify-write operations from the client. In order to make this guarantee the clients need to be online.
Batched writes are a limited kind of transaction that exists specifically to allow users to be able to change multiple documents in an all-or-nothing manner even while offline.
If you don't need or want to be able to write while offline just use a regular transaction: it will handle checking whether or not you're online and will fail if you're offline. You're not obligated to read anything in the transaction.
Checking the result in the OnCompleteListener for a batch write does not work for this purpose. You couldn't prevent anything in that callback because it is only called once the write has been successfully applied to the server.