Internet Connection State in Android apps
Internet connection sometimes can be crucial to your applications. You can inform a user with the banner stating that there is no internet connection, disconnect from the socket and restore a connection when the internet reappears, block HTTP/HTTPS requests, or even disable certain features.
For that we will use Android SDK
What are we dealing with?
Android supports different types of network connections. It includes but is not limited to wifi, cellular, ethernet, and VPN connections.
Even you can have multiple network connections simultaneously, but only one of them will be active. That means if we have both cellular and wifi connections, Android chooses an active connection by prioritizing one of them.
We can disable the currently active network. Hence if there is another available network, Android needs some time to switch to that network.
Android devices can also go to sleep mode and disable all network operations.
So, suppose we want to implement a robust, reusable solution. In that case, the final result should check if the internet connection is active, notify users if the internet connection changes, and cover all possible edge cases.
Synchronous approach--->
To check if we currently have an active network connection, you can access the?activeNetworkInfo?in the?ConnectivityManager?class. It will look something like this-->
https://gist.github.com/codewith-fun/34fd1b97b2c94ac395d75d22f3ee7e12
https://gist.github.com/codewith-fun/34fd1b97b2c94ac395d75d22f3ee7e12
private val connectivityManager: ConnectivityManager = context.getSystemService()!
val isNetworkConnected: Boolean
get() = connectivityManager.activeNetworkInfo?.isConnected ?: false!
But the?NetworkInfo?and the?activeNetworkInfo?were deprecated in API level 29.
So, what is the preferred way to check if the active network is connected now?
activeNetworkInfo?was replaced with the?activeNetwork, which is the?Network?object. The?Network?object provides broader functionality, but most importantly, we can use it to replace the?NetworkInfo?approach to check synchronously if we currently have an active connection.
To do this, we need to retrieve the?NetworkCapabilities?from the active network and validate whether the?NetworkCapabilities?satisfy our requirements.
I used an extension in my example.
领英推荐
private val connectivityManager: ConnectivityManager = context.getSystemService()!
val isNetworkConnected: Boolean
get() = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
.isNetworkCapabilitiesValid()
private fun NetworkCapabilities?.isNetworkCapabilitiesValid(): Boolean = when {
this == null -> false
hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) &&
(hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
hasTransport(NetworkCapabilities.TRANSPORT_VPN) ||
hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) -> true
else -> false
}
NetworkCapabilities.NET_CAPABILITY_INTERNET?means that Internet connectivity was successfully detected, while the?NetworkCapabilities.NET_CAPABILITY_VALIDATED?indicates that Android OS successfully validated connectivity on this network.
We should check if both capabilities are present to verify that we have a?Network?with an internet connection.
Listening to network state change
Since we have seen two synchronous approaches, we can’t use any of them to listen to the network state change events.
Luckily, Android SDK provides a variety of options to do that.
Two main functions are?registerNetworkCallback?and?registerDefaultNetworkCallback.
The essential difference between them is that the?registerDefaultNetworkCallback?listens only to the active network state.
At the same time, the?registerNetworkCallback?can be configured to listen to the specific networks, e.g., wifi connection, and also can listen to a couple of networks simultaneously.
Both functions accept?ConnectivityManager.NetworkCallback?as a parameter. There is a comprehensive explanation in the Android documentation on when each callback gets called. Recommended for reading -?link.
Here is an example of how you can use it:
private val networkCallback = ConnectivityManager.NetworkCallback()
override fun onAvailable(network: Network) {
// Called when network is ready to use
}
override fun onLost(network: Network) {
// Called when network disconnects
}
override fun onUnavailable() {
// Called if the requested network request cannot be fulfilled
}
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) {
// Called when the network corresponding to this request changes capabilities
// For example, "NetworkCapabilities.NET_CAPABILITY_VALIDATED" might appear with a delay.
}
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
// Called when access to the specified network is blocked or unblocked.
// This function can be called, for example, when the device goes to the sleep mode.
}
override fun onLosing(network: Network, maxMsToLive: Int) {
// Called when the network is about to be lost
}
override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
// Called when the network corresponding to this request changes LinkProperties
}
}
fun startListenNetworkState() {
connectivityManager.registerDefaultNetworkCallback(networkCallback)
}
fun stopListenNetworkState() {
connectivityManager.unregisterNetworkCallback(networkCallback)
}
Here is my implementation of the internet connection state listener with Hilt and Kotlin coroutines.
interface NetworkConnectionManager
/**
* Emits [Boolean] value when the current network becomes available or unavailable.
*/
val isNetworkConnectedFlow: StateFlow<Boolean>
val isNetworkConnected: Boolean
fun startListenNetworkState()
fun stopListenNetworkState()
}
@Singleton
class NetworkConnectionManagerImpl @Inject constructor(
@ApplicationContext context: Context,
coroutineScope: CoroutineScope
) : NetworkConnectionManager {
private val connectivityManager: ConnectivityManager = context.getSystemService()!!
private val networkCallback = NetworkCallback()
private val _currentNetwork = MutableStateFlow(provideDefaultCurrentNetwork())
override val isNetworkConnectedFlow: StateFlow<Boolean> =
_currentNetwork
.map { it.isConnected() }
.stateIn(
scope = coroutineScope,
started = SharingStarted.WhileSubscribed(),
initialValue = _currentNetwork.value.isConnected()
)
override val isNetworkConnected: Boolean
get() = isNetworkConnectedFlow.value
override fun startListenNetworkState() {
if (_currentNetwork.value.isListening) {
return
}
// Reset state before start listening
_currentNetwork.update {
provideDefaultCurrentNetwork()
.copy(isListening = true)
}
connectivityManager.registerDefaultNetworkCallback(networkCallback)
}
override fun stopListenNetworkState() {
if (!_currentNetwork.value.isListening) {
return
}
_currentNetwork.update {
it.copy(isListening = false)
}
connectivityManager.unregisterNetworkCallback(networkCallback)
}
private inner class NetworkCallback : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
_currentNetwork.update {
it.copy(isAvailable = true)
}
}
override fun onLost(network: Network) {
_currentNetwork.update {
it.copy(
isAvailable = false,
networkCapabilities = null
)
}
}
override fun onUnavailable() {
_currentNetwork.update {
it.copy(
isAvailable = false,
networkCapabilities = null
)
}
}
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) {
_currentNetwork.update {
it.copy(networkCapabilities = networkCapabilities)
}
}
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
_currentNetwork.update {
it.copy(isBlocked = blocked)
}
}
}
/**
* On Android 9, [ConnectivityManager.NetworkCallback.onBlockedStatusChanged] is not called when
* we call the [ConnectivityManager.registerDefaultNetworkCallback] function.
* Hence we assume that the network is unblocked by default.
*/
private fun provideDefaultCurrentNetwork(): CurrentNetwork {
return CurrentNetwork(
isListening = false,
networkCapabilities = null,
isAvailable = false,
isBlocked = false
)
}
private data class CurrentNetwork(
val isListening: Boolean,
val networkCapabilities: NetworkCapabilities?,
val isAvailable: Boolean,
val isBlocked: Boolean
)
private fun CurrentNetwork.isConnected(): Boolean {
// Since we don't know the network state if NetworkCallback is not registered.
// We assume that it's disconnected.
return isListening &&
isAvailable &&
!isBlocked &&
networkCapabilities.isNetworkCapabilitiesValid()
}
private fun NetworkCapabilities?.isNetworkCapabilitiesValid(): Boolean = when {
this == null -> false
hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) &&
(hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
hasTransport(NetworkCapabilities.TRANSPORT_VPN) ||
hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) -> true
else -> false
}
}{
Just call the?startListenNetworkState()?function to start listening to the internet connection state.
For most use cases, it should be enough to call this function once during the application lifecycle. We could do it in the class constructor because we marked our class as a Singleton, like so:
init
}
startListenNetworkState()
{
Note:- registerDefaultNetworkCallback?could be replaced with the more modern?registerBestMatchingNetworkCallback?function added in API level 31.