Monitoring Network Connectivity With LiveData

Monitoring network connectivity is something that we as mobile developers have to do while trying to provide the best user experience in our apps. Android provides us with tools to do this and I have to admit, before I simply used to use the connectivity manager to handle this in a reactive way (not Rx!).

This basically meant that I would implement my network connectivity checks similarly across apps in a way that resembled the below code snippet:

    fun isNetworkConnected(context: Context): Boolean {
        val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val activeNetworkInfo: NetworkInfo? = connectivityManager.activeNetworkInfo
        return activeNetworkInfo?.isConnected ?: false
    }

This did the network connectivity check after an event was fired to the system such as a button click or something. While this method can be useful , Android docs have since gone to deprecate the NetworkInfo class and related methods and included this note (excerpt) :

Apps should instead use the ConnectivityManager.NetworkCallback API to learn about connectivity

The NetworkCallback API allows us to listen to connection changes as mentioned above, and with using LiveData from architecture components, we’re able to react to network changes in a ui friendly manner.

Below i’ve written a small implementation of the NetworkCallback API in a class that has extended LiveData. This way we can monitor the methods we have implemented from the API, and post the network state via LiveData to anyone that is subscribed to these events. I have supplied a ConnectivityManager instance via dagger2. I have also made the NetworkStateMonitor class available via dagger2 so I can inject this to any ViewModel that wished to make the class available to the views that need these events.

@Singleton
class NetworkStateMonitor @Inject constructor(private val cm: ConnectivityManager) :
    LiveData<NetworkState>() {

    private var hasNetworkChanged: Boolean = false

    private val networkStateObject = object : ConnectivityManager.NetworkCallback() {
        override fun onLost(network: Network) {
            super.onLost(network)
            hasNetworkChanged = true
            postValue(NetworkState.CONNECTION_LOST)
        }

        override fun onUnavailable() {
            super.onUnavailable()
            hasNetworkChanged = true
            postValue(NetworkState.DISCONNECTED)
        }

        override fun onAvailable(network: Network) {
            super.onAvailable(network)
            if(hasNetworkChanged) {
                postValue(NetworkState.CONNECTED)
            }
        }
    }

    override fun onInactive() {
        super.onInactive()
        cm.unregisterNetworkCallback(networkStateObject)
    }

    override fun onActive() {
        super.onActive()
        cm.registerNetworkCallback(networkRequestBuilder(), networkStateObject)
    }

    private fun networkRequestBuilder(): NetworkRequest {
        return NetworkRequest.Builder()
            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
            .build()
   

The reason why we have that boolean there is that we don’t need to show the user they already have an active network connection when nothing has changed, so the “Connected!” notification in this case will only be shown when an actual network change had occurred.

Under the addTransportType options we’ve added the capabilities of monitoring both Wi-Fi and Mobile Data. Depending on your scenario, you may just want to monitor either one of those two.

Extending LiveData for network callbacks, we’re able to both register our callback when we have someone subscribed to updates then unregister when this is no longer needed.

From our ViewModel side we supply our network monitor via dagger as well and return the LiveData to the ui:

class MainActivityViewModel @Inject constructor(private val networkMonitor: NetworkStateMonitor) : ViewModel() { 

   fun getNetworkMonitorLiveData(): LiveData<NetworkState> = 
    networkMonitor
}

From our Activity/Fragment side we can do something like this:

    private fun setupNetworkObserver() {
        viewModel.getNetworkMonitorLiveData().observe(this, Observer {
            when (it) {
                NetworkState.CONNECTED -> {
                    Snackbar.make(
                        binding.coordinatorLayoutParent,
                        getString(R.string.network_connected),
                        Snackbar.LENGTH_LONG
                    ).show()
                }
                NetworkState.CONNECTION_LOST -> {
                    Snackbar.make(
                        binding.coordinatorLayoutParent,
                        getString(R.string.network_connection_lost),
                        Snackbar.LENGTH_LONG
                    ).show()
                }
                NetworkState.DISCONNECTED -> {
                    Snackbar.make(
                        binding.coordinatorLayoutParent,
                        getString(R.string.network_disconnected),
                        Snackbar.LENGTH_LONG
                    ).show()
                }
                NetworkState.CONNECTING -> {
                    Snackbar.make(
                        binding.coordinatorLayoutParent,
                        getString(R.string.network_connecting),
                        Snackbar.LENGTH_LONG
                    ).show()
                }
                else -> {
                    Snackbar.make(
                        binding.coordinatorLayoutParent,
                        getString(R.string.network_unknown),
                        Snackbar.LENGTH_LONG
                    ).show()
                }
            }
        })
    }

Having users know when network connectivity has failed as they’re using your app is crucial for the user experience. While the previous method can work and was good at handling errors in the instance of something failing to load, if the user is transacting in your app and connectivity drops, this way they’re kept informed with what’s going on and they’re able to react appropriately.

Thanks to Peter-John Welcome for proof-reading this article

2
Leave a Reply

avatar
1 Comment threads
1 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
2 Comment authors
Vusi MoyoJacques Smuts Recent comment authors
  Subscribe  
newest oldest most voted
Notify of
Jacques Smuts
Guest

Great article!

One thing I think is worth mentioning is that the Android Connectivity Monitor can inform you whether or not the device is connected to an internet-active network, but it doesn’t tell you whether or not that network actually has access to the desired endpoint. If you’re behind The Great Firewall there’s a good chance your device is unable to access the api, even if it is online.