Making Android Jetpack’s NetworkBoundResource work in offline mode (in Kotlin!)
This article was published in Android Pub and featured in the Mindorks newsletter.
The Jetpack article, Guide to App Architecture, describes an algorithm for providing data to an app by either retrieving sufficiently recent data from a local cache, or loading the latest data from the network. The article provides code snippets illustrating how such an implementation might look. The NetworkBoundResource class is at the core of the implementation, and using the algorithm only requires one to subclass NetworkBoundResource and override a few methods, usually requiring only a few lines of code.
While the algorithm handled loading, success, and error network states, its intended use case is only for devices in online mode. In offline mode, the algorithm may result in a failed network call and error state. Recently, there have been a number of articles on the importance of gracefully handling devices in offline mode, due to network unavailability or user choice. The classic example is a user travelling in areas where network availability is unreliable. Another example is where the user only wants to use Wi-Fi data, expecting the app to provide cached data should no Wi-Fi network be available.
The User Experience of Offline
One of the challenges of handling offline mode relates to changing back to online mode with minimal impact on the user experience. Reauthentication should happen in the background. The user should only be prompted to login again when absolutely necessary, such as when the password has changed.
The Reworked NetworkBoundResource
The above decision tree illustrates the flow of logic in the reworked algorithm:
1. If the device is offline, any cached values should immediately be dispatched.
2. Otherwise, any cached values should be dispatched as part of a temporary loading state.
3. The algorithm then determines whether the cached values are sufficiently recent, or whether the data should be fetched from the network. If sufficiently recent, the cached values are dispatched.
4. Otherwise, the algorithm determines whether a login is necessary (for example, if an authentication token is missing or expired), and if so, attempts to reauthenticate.
If the login fails because the credentials are invalid, the user is prompted to re-enter the credentials, and the algorithm stops. Once the user has entered the credentials, the algorithm should be automatically restarted, without the user having to refresh.
5. The data is now fetched over the network. If the call was unsuccessful, the reason is evaluated. If the call failed because it was unauthorized, it is necessary to reauthenticate and redo the call. Otherwise, the error should be dispatched.
6. If the call was successful, the data is saved, automatically triggering a refresh.
The sample implementation omits the isOnline, shouldLogin, autoReAuthenticate, and loadFromNetwork methods as they are highly specific to your implementation of the app, but not the subclasses of the app’s NetworkBoundResource. Since I was developing a Lollipop (API 21) app, I was able to use the ConnectivityManager to query network state and receive notifications when the network state changes.
Once you have fully implemented these methods, each NetworkBoundResource subclass will require very little additional code, simply overriding loadFromDb, getDataFetchDate, shouldFetch, createCall, and saveCallResult.
With thanks: Jaco de Wet (UX designer), Frans Stofberg (Architect), Peter-John Welcome (Engineering Lead) for their ideas and support.