15.
Realtime Database Offline Capabilities
Written by Harun Wangereka
So far, you’ve created an app that enables you to save posts to the database and read the posts from the database. The next step is to implement offline support. If you turn off your internet connection now and run your app, you’d see an empty screen because your app can’t fetch the data from the database.
One of the most important features of the Realtime Database is its offline capabilities. If you were creating your backend system, you would need to persist the data by yourself. Firebase handles that for you, and it enables your app to work properly even when the user loses network connection. In this chapter, you’ll learn how exactly it does that, and you’ll add offline support to your app so that you could see posts on the screen and even add posts while you are offline.
Setting up Firebase
If you skipped previous chapters, you need to set up Firebase to follow along. Do the following steps:
- Create a project in the Firebase console.
- Enable Google sign-in.
- Create a new Realtime Database. Set security rules to the test mode to allow everyone read and write access. You should never use these settings for your production apps.
- Add google-service.json to both starter and final project’s root directory.
If you need a reminder of how to do this, go back to Chapter 12: “Firebase Overview” and Chapter 13: “Introduction to Firebase Realtime Database.”
Be sure to use the starter project from this chapter, by opening the realtime-database-offline-capabilities/projects/starter rather than continuing with the final project you previously worked on. It has a few things added to it, including placeholders for the code that you’ll add in this chapter.
Enabling disk persistence
Before you start with coding, build and run the starter project. Make sure your device is connected to the Internet.
If you aren’t logged in, do so. After successful login, you’ll see your posts on the home screen. Open any post, disconnect your mobile device from the network and navigate back to the home screen.
Note: The post and comments on screenshots can differ a bit than yours. You can use any post content you like!
Notice that your posts are still there. By default, Firebase stores your data in-memory. Now, close the app and kill the app process from the Recent apps menu. Run your app again. Now, you’ll see an empty screen.
Caching data locally
To enable disk persistence, you only need one line of code. Open WhatsUpApplication.kt and add the following at the end of onCreate():
FirebaseDatabase.getInstance().setPersistenceEnabled(true)
Setting the argument of setPersistenceEnabled() to true enables the app to store the data to the device’s local storage — the disk. That is what makes the data available even after you kill the app.
The reason you had to enable disk persistence in the Application class, instead of the RealtimeDatabaseManager, is that setPersistenceEnabled() needs to be called once per app and before creating the first database reference.
Turn on the network connection on your device. Build and run. Your posts will appear on the home screen.
Now, disconnect your mobile phone from the network, close the app and kill the app process from the Recent apps menu. Run your app again. You’ll see the posts appearing on the home screen again. Nice job!
Writing data when offline
To test this case, disconnect your mobile device from the network and create a new post with the text “Newly added post to test persistence.”. You’ll see that the post appears on the home screen.
Then, open the Firebase console and check if your post is saved to the database. You’ll notice that the post isn’t there since you are offline and there is no connection to the database. But where is the post stored then?
Now, connect your mobile phone to the network and observe the database data in the console. You’ll see that a few moments after you connect your app back to the Internet your post appears in the database.
Setting the argument of setPersistenceEnabled() to true also keeps track of all the writes you initiated while you were offline and then when the network connection comes back, it resends all the write operations. This makes the user experience optimal even if the user loses the network connection for a moment because your app works as if it’s connected to the Internet. After all, it uses local data from the disk for synchronization.
Enabling persistence also ensures any data or changes made while offline are kept even across phone restarts. All operations are queued and sent to the Firebase Realtime Database server once the app has a connection.
Keeping data in sync
Realtime Database stores a copy of the data, locally, only for active listeners. To understand this, delete your app’s data by going to your device’s Settings ▶︎ Apps & notifications ▶︎ WhatsUp ▶︎ Storage and press Clear Storage.
Make sure you’re connected to the Internet and the posts are visible on the home screen. Then, disconnect the device from the Internet and open any post that you know has comments. You’ll notice that there are no comments displayed even if you instructed the app to store data locally. Since Realtime Database stores data locally only for active listeners, your comments weren’t saved because you haven’t accessed them yet.
Don’t worry, you’ll fix this issue in a few moments.
Open RealtimeDatabaseManager.kt and remove private modifier from COMMENTS_REFERENCE. Your constant now looks like this:
const val COMMENTS_REFERENCE = "comments"
To save data locally for the location that has no active listeners attached, open WhatsUpApplication.kt , find onCreate() and change calls on the database instance to include keepSynced():
FirebaseDatabase.getInstance().apply {
setPersistenceEnabled(true)
getReference(COMMENTS_REFERENCE).keepSynced(true)
}
Now, Realtime Database will download the comments and keep them in sync, even if there are no active listeners at that location. Whatever happens at this location — either data gets deleted or updated, you’ll receive an update locally, as well.
Build and run. Connect your mobile device to the network, and after posts are loaded, disconnect your app from the network. Now open any post that you know has comments. You’ll see your comments this time.
Default cache size is 10MB, which allows you to store a substantial amount of data locally, and, in most cases, this should be enough. If you exceed that limit, any data that hasn’t been used for a long time will be deleted. So it’s an LRU cache kind of mechanism.
However, in a multi-user app, there is a huge chance of reaching race conditions. For example, if two users aren’t connected to the Internet, and both create a post, one later than the other, and if they finally connect to the Internet at the same time, whichever user has a better and faster connection will write to the database first. After that, the other user can overwrite existing data in the database. This is important to know because, usually, this isn’t the desired behavior.
Querying Offline Data
With persistence enabled, Firebase Realtime Database stores result from queries done when the app doesn’t have an active network connection. The queries are done on data that has been previously loaded. It firsts loads data from the cache. Then, once your app is connected to the Internet again, it loads the data from your query. A query can return some items while offline. Then when online, more items will be added to your query results.
Other offline scenarios and network connectivity features
Firebase has many features that can help you when in offline mode and connectivity are an important part of your app. The features you’re about to learn apply to your app regardless if the local offline persistence is enabled or not.
Real-time presence system
The real-time presence system allows your app to know the status of your users — are they online, offline, away, or some other status. This feature is inevitable for chat applications for example, because you want to know if the person you’re texting is online. This feature may seem simple, but to build an entire app infrastructure or a mechanism, which handles this for you, can be quite troublesome.
Firebase has this infrastructure implemented and it allows you to use it out of the box. Firebase saves the user presence status info to the /.info/connected location that you can observe just like any other location in the database. The .info/connected reference just contains a boolean which indicates if the client is connected or not. The problem appears if you want to write something to the database when the user status changes to the offline status. For this case, you can use onDisconnect() from Firebase. This method tells the Firebase server to do something when it notices that the client isn’t online anymore. It works properly, even in cases when the app crashes or the connection is lost, or any other nasty edge case.
On Android, Firebase automatically manages the connection state to optimize battery usage and reduce bandwidth. If the client app doesn’t have any connection to the database, no active listeners, no pending requests, or similar, Firebase will automatically close the connection after 60 seconds of inactivity. Alternatively, you can explicitly close the connection by using goOffline().
To learn more about the presence feature, check the official documentation (https://firebase.google.com/docs/database/android/offline-capabilities#section-presence).
Latency
Generally speaking, latency is the time delay between the cause and the effect of some change. In Realtime Database that would be, for example, when the user triggers disconnecting from the server until the disconnecting action is done, or the delay between requesting a login entry, to an actual authorization response.
Firebase handles latency in a way that it stores a timestamp that is generated on the server as data in a value called TIMESTAMP. The timestamp is a static field in the ServerValue class and you access it by calling ServerValue.TIMESTAMP. You can use it to reliably know the exact time when the action actually finishes.
Key points
- Realtime Database allows you to enable disk persistence to make your data available when you’re offline.
- Enabling disk persistence also tracks all the writes you initiated while you were offline and then when the network connection comes back it synchronizes all the write operations.
- Realtime Database stores a copy of the data, locally, only for active listeners. You can use
keepSynced()on a database reference to save data locally for the location that has no active listeners attached. - You can be able to query data that is available offline. Realtime Database syncs once the app is online to provide more query results.
- Firebase provides you with a real-time presence system, which allows your app to know the status of your users, are they online or offline.
- Firebase handles latency in a way that stores a timestamp, that is generated on the server, as data when the client disconnects and lets you use that data to reliably know the exact time when the user disconnected.
Where to go from here?
In this chapter, you learned how Firebase works offline and what features it provides to help you handle offline mode and connectivity issues. You also improved your app’s user experience in a way that you enabled your app to work as expected even if the user is not connected to the Internet.
For more info and examples on enabling offline capabilities, visit the official documentation (https://firebase.google.com/docs/database/android/offline-capabilities).
Chapter 16: “Usage & Performance” is the last chapter about Realtime Database and it will teach you more about its performance and limits.