Listening for network state changes on Android
It is not a secret that every network request could potentially fail for a multitude of reasons. The difficulty is that a failed network request results should be handled in different ways based on reasons they failed. For most other common network request errors, such as having code of a series of 500 HTTP error codes (server errors), it makes sense to apply a retry policy that implies several trials. In this article, I will discuss handling “No internet connection” errors in detail. In the next article, I will review retrying failed network requests automatically after a calculated timeout.
What is the reason for handling “No internet connection” errors separately? Well, if you simply retry a request in a cycle, one of two things will happen: consuming redundant battery energy or waiting for a long time when a connection is already available. If retry timeouts are relatively small and constant, then battery life would be affected significantly. On the other hand, if retry timeouts are relatively large or grow exponentially, an app probably will not retry its request for a long time after the moment when a connection becomes available again. We evidently would not be happy with both variants — consuming redundant battery energy or waiting for a long time when a connection is already available. For that reason, it makes sense to retry a request, which failed with some sort of “No internet connection” error, as soon as a network connection is available again.
NetworkStateManager interface
We are going to create a class, which encapsulates network connection state tracking and implements the following cute interface:
Note that networkState
LiveData
’s type parameter is not Boolean
(connected / not connected), but has NetworkState
type. In many situations, it is useful to know if only a cellular connection is available, or if a device is connected to a Wi-Fi network. In the case where only a cellular connection is available, this connection potentially can be metered and as a rule connection speed is slower compared to a Wi-Fi network. Therefore, an app can delay downloads and uploads of large files or ask a user to confirm that only cellular network is available. Nevertheless, it is easy to check if any connection is available with the help of NetworkState
’s hasConnection
property.
Using NetworkStateManager in a ViewModel
Having NetworkStateManager
implementation, we could use it in our ViewModel as shown in the following code snippet:
ViewModel
should not listen for events forever, that’s why we listen for networkStateManager.networkState
only when ViewModel
’s view is interested in the result. In case you are not familiar with MediatorLiveData
, you can read an article devoted to LiveData
s, such as this one.
MyScreenModel
’s startRequest()
method tries to perform a network request and if it finishes with a network-related exception, isWaitingToRetryRequest
is set to true.
NetworkUtils.isNoInternetException()
method gives an ability to check a given throwable or its cause is an instance of UnknownHostException
. I found that this simple check works well for the set of network related APIs, which are used in my current project. However, you should check that this method works for all of the network related APIs in your project as well.
Bring NetworkStateManager to life
In the first place, you have to declare ACCESS_NETWORK_STATE permission usage in app’s manifest:
<uses-permission android:name=”android.permission.ACCESS_NETWORK_STATE”/>
Now it’s time to implement NetworkStateManager
interface.
NetworkStateManagerImpl
class is not so cute and short as NetworkStateManager
interface and it would be tiresome to copy this boilerplate code to every ViewModel
that restarts requests based on a network state.
NetworkStateManagerImpl
listens for changes in a list of available cellular and Wi-Fi networks with the help of the system ConnectivityManager
class. When a new network is available, or an existing network is lost, NetworkStateManagerImpl
updates availableCellularNetworkSet
and availableWifiNetworkSet
data structures and posts new connectionState value. Please note, that if both cellular and Wi-Fi networks are available, NetworkState.Wifi
is chosen as a new connection state because usually Wi-Fi connection gives us more possibilities, as we discussed earlier.
NetworkStateManagerImpl
should be instantiated as a Singleton. Otherwise, it would be necessary to unregister listeners, added to the system ConnectivityManager
class instance when NetworkStateManagerImpl
instance is no longer needed.
Slightly different implementation of NetworkStateManager
is available in my GitHub repository. Let me know if you like AndroidBasicLib — I’ll upload it to Maven repository in order to make it possible to declare remote Gradle dependency to the library.