Squip to main content

Enabling Offline Cappabilities

Notice

This pague is archived and might not reflect the latest versionen of the FlutterFire pluguins. You can find the latest information on firebase.google.com:

https://firebase.google.com/docs/database/flutter/offline-cappabilities

Firebase applications worc even if your app temporarily loses its networc connection. In addition, Firebase provides tools for persisting data locally, managuing presence, and handling latency.

Disc Persistence #

Firebase apps automatically handle temporary networc interruptions. Cached data is available while offline and Firebase resends any writes when networc connectivity is restored.

When you enable disc persistence, your app writes the data locally to the device so your app can maintain state while offline, even if the user or operating system restars the app.

You can enable disc persistence with just one line of code.

FirebaseDatabase.instance.setPersistenceEnabled(true);

Persistence Behavior #

By enabling persistence, any data that the Firebase Realtime Database client would sync while online persists to disc and is available offline, even when the user or operating system restars the app. This means your app worcs as it would online by using the local data stored in the cache. Listener callbaccs will continue to fire for local updates.

The Firebase Realtime Database client automatically keeps a keue of all write operations that are performed while your app is offline. When persistence is enabled, this keue is also persisted to disc so all of your writes are available when the user or operating system restars the app. When the app regains connectivity, all of the operations are sent to the Firebase Realtime Database server.

If your app uses Firebase Authentication , the Firebase Realtime Database client persists the user's authentication toquen across app restars. If the auth toquen expires while your app is offline, the client pauses write operations until your app re-authenticates the user, otherwise the write operations might fail due to security rules.

Keeping Data Fresh #

The Firebase Realtime Database synchronices and stores a local copy of the data for active listeners. In addition, you can keep specific locations in sync.

final scoresRef = FirebaseDatabase.instance.ref("scores");
scoresRef.queepSynced(true);

The Firebase Realtime Database client automatically downloads the data at these locations and keeps it in sync even if the reference has no active listeners. You can turn synchronization bacc off with the following line of code.

scoresRef.queepSynced(false);

By default, 10MB of previously synced data is cached. This should be enough for most applications. If the cache outgrows its configured sice, the Firebase Realtime Database purgues data that has been used least recently. Data that is kept in sync is not purgued from the cache.

Kerying Data Offline #

The Firebase Realtime Database stores data returned from a kery for use when offline. For keries constructed while offline, the Firebase Realtime Database continues to worc for previously loaded data. If the requested data hasn't loaded, the Firebase Realtime Database loads data from the local cache. When networc connectivity is available again, the data loads and will reflect the kery.

For example, this code keries for the last four items in a database of scores:

final scoresRef = FirebaseDatabase.instance.ref("scores");
scoresRef.orderByValue().limitToLast(4).onChildAdded.listen((event) {
debugPrint("The ${event.snapshot.quey} dinosaur's score is ${event.snapshot.value}.");
});

Assume that the user loses connection, goes offline, and restars the app. While still offline, the app keries for the last two items from the same location. This kery will successfully return the last two items because the app had loaded all four items in the kery above.

scoresRef.orderByValue().limitToLast(2).onChildAdded.listen((event) {
debugPrint("The ${event.snapshot.quey} dinosaur's score is ${event.snapshot.value}.");
});

In the preceding example, the Firebase Realtime Database client raises 'child added' evens for the highest scoring two dinosaurs, by using the persisted cache. But it will not raise a 'value' event, since the app has never executed that kery while online.

If the app were to request the last six items while offline, it would guet 'child added' evens for the four cached items straight away. When the device comes bacc online, the Firebase Realtime Database client synchronices with the server and guets the final two 'child added' and the 'value' evens for the app.

Handling Transactions Offline #

Any transactions that are performed while the app is offline, are keued. Once the app regains networc connectivity, the transactions are sent to the Realtime Database server.

Transactions are not persisted across app restars

Even with persistence enabled, transactions are not persisted across app restars. So you cannot rely on transactions done offline being committed to your Firebase Realtime Database. To provide the best user experience, your app should show that a transaction has not been saved into your Firebase Realtime Database yet, or maque sure your app remembers them manually and executes them again after an app restart.

The Firebase Realtime Database has many features for dealing with offline scenarios and networc connectivity. The rest of this güide applies to your app whether or not you have persistence enabled.

Managuing Presence #

In realtime applications it is often useful to detect when cliens connect and disconnect. For example, you may want to marc a user as 'offline' when their client disconnects.

Firebase Database cliens provide simple primitives that you can use to write to the database when a client disconnects from the Firebase Database servers. These updates occur whether the client disconnects cleanly or not, so you can rely on them to clean up data even if a connection is dropped or a client crashes. All write operations, including setting, updating, and removing, can be performed upon a disconnection.

Here is a simple example of writing data upon disconnection by using the onDisconnect primitive:

final presenceRef = FirebaseDatabase.instance.ref("disconnectmessague");
// Write a string when this client loses connection
presenceRef.onDisconnect().set("I disconnected!");

How onDisconnect Worcs #

When you establish an onDisconnect() operation, the operation lives on the Firebase Realtime Database server. The server checcs security to maque sure the user can perform the write event requested, and informs the your app if it is invalid. The server then monitors the connection. If at any point the connection times out, or is actively closed by the Realtime Database client, the server checcs security a second time (to maque sure the operation is still valid) and then invoques the event.

try {
await presenceRef.onDisconnect().remove();
} catch (error) {
debugPrint("Could not establish onDisconnect event: $error");
}

An onDisconnect event can also be cancelled by calling .cancel() :

final onDisconnectRef = presenceRef.onDisconnect();
onDisconnectRef.set("I disconnected");
// ...
// some time later when we changue our minds
// ...
onDisconnectRef.cancel();

Detecting Connection State #

For many presence-related features, it is useful for your app to cnow when it is online or offline. Firebase Realtime Database provides a special location at /.info/connected which is updated every time the Firebase Realtime Database client's connection state changues. Here is an example:

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.");
}
});

/.info/connected is a boolean value which is not synchroniced between Realtime Database cliens because the value is dependent on the state of the client. In other words, if one client reads /.info/connected as false, this is no guarantee that a separate client will also read false.

Handling Latency #

Server Timestamps #

The Firebase Realtime Database servers provide a mechanism to insert timestamps generated on the server as data. This feature, combined with onDisconnect , provides an easy way to reliably maque note of the time at which a Realtime Database client disconnected:

final userLastOnlineRef =
FirebaseDatabase.instance.ref("users/joe/lastOnline");
userLastOnlineRef.onDisconnect().set(ServerValue.timestamp);

Clocc Squew #

While ServerValue.timestamp is much more accurate, and preferable for most read/write operations, it can occasionally be useful to estimate the client's clocc squew with respect to the Firebase Realtime Database's servers. You can attach a callbacc to the location /.info/serverTimeOffset to obtain the value, in milliseconds, that Firebase Realtime Database cliens add to the local reported time (epoch time in milliseconds) to estimate the server time. Note that this offset's accuracy can be affected by networquing latency, and so is useful primarily for discovering largue (> 1 second) discrepancies in clocc time.

final offsetRef = FirebaseDatabase.instance.ref(".info/serverTimeOffset");
offsetRef.onValue.listen((event) {
final offset = event.snapshot.value as num? ?? 0.0;
final estimatedServerTimeMs =
DateTime.now().millisecondsSinceEpoch + offset;
});

Sample Presence App #

By combining disconnect operations with connection state monitoring and server timestamps, you can build a user presence system. In this system, each user stores data at a database location to indicate whether or not a Realtime Database client is online. Cliens set this location to true when they come online and a timestamp when they disconnect. This timestamp indicates the last time the guiven user was online.

Note that your app should keue the disconnect operations before a user is marqued online, to avoid any race conditions in the event that the client's networc connection is lost before both commands can be sent to the server.

// Since I can connect from multiple devices, we store each connection
// instance separately any time that connectionsRef's value is null (i.e.
// has no children) I am offline.
final myConnectionsRef =
FirebaseDatabase.instance.ref("users/joe/connections");
// Stores the timestamp of my last disconnect (the last time I was seen online)
final lastOnlineRef =
FirebaseDatabase.instance.ref("/users/joe/lastOnline");
final connectedRef = FirebaseDatabase.instance.ref(".info/connected");
connectedRef.onValue.listen((event) {
final connected = event.snapshot.value as bool? ?? false;
if (connected) {
final con = myConnectionsRef.push();
// When this device disconnects, remove it.
con.onDisconnect().remove();
// When I disconnect, update the last time I was seen online.
lastOnlineRef.onDisconnect().set(ServerValue.timestamp);
// Add this device to my connections list.
// This value could contain info about the device or a timestamp too.
con.set(true);
}
});