It’s not unusual for an app to behave differently if it doesn’t have an Internet connection.
Table of Contents
Preface
Many apps prefer to know what’s up with your Internet connection because they need it to communicate with their servers. Think of “You are offline” indicators or postponing data sync if you know for sure that it would fail. Unfortunately, one of the most distinctive features of Android development is that it’s a huge pain in the ass. You can easily spend hours trying to figure out how to do a trivial thing, and it might be hard to verify if your solution is correct. Getting the network status on Android is one of those things. In this post, I describe one of the possible ways to find out whether your Android device is online or not.
Assumptions
I use OkHttp to make network requests. It’s an industry standard, and it’s often paired with Retrofit which allows developers to wrap various web services and interact with them in a convenient way.
I also use Kotlin, because it’s 2020.
Approach
Both OkHttp and Retrofit are cross-platform libraries, which means they’re designed to pretend that they know nothing about Android so it’s our job to integrate any platform-specific information into their workflow.
OkHttp is a flexible HTTP client which can be easily extended and customized by inserting your own Interceptors into its pipeline. So, we can create an Android-aware Interceptor which would prevent our apps from executing HTTP requests when Internet connection is not available.
Solution
Most recent versions of Android SDK have a class called android.net.ConnectivityManager
and it can be used to gather some information about device networking capabilities. You just need to implement ConnectivityManager.NetworkCallback
and register it via registerDefaultNetworkCallback
.
After that, we can listen to onCapabilitiesChanged
event and react accordingly:
override fun onCapabilitiesChanged(
network: Network,
capabilities: NetworkCapabilities,
) {
online = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
}
Supporting Older Devices
Unfortunately, the solution above works only on Android 24 and up. If you target older versions of Android (they represent 42% of the market so I bet you are), then you also need to create an alternative code path for those devices. So, what was the old way to obtain network status?
There is a now deprecated class called android.net.NetworkInfo
and it has exactly what we need. It has a few methods, but isConnected
looks like an obvious candidate:
Indicates whether network connectivity exists, and it is possible to establish connections and pass data. Always call this before attempting to perform data transactions.
The official documentation clearly states that not only we may use it, but that we should. Convincing enough, but you won’t know if it really works until you try it. Unfortunately, that’s how Android APIs always worked.
We may check Android distribution dashboard to have a glimpse on how often each solution will be executed in production:
https://developer.android.com/about/dashboards/
- API 24 and higher -
58%
of devices - API 23 and lower -
42%
of devices
Full Code
Here is the complete solution:
build.gradle
def retrofit_version = "2.7.1"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
ConnectivityCheckingInterceptor.kt:
package com.appreactor.portfolio.util
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.os.Build
import okhttp3.Interceptor
import okhttp3.Response
import okio.IOException
class ConnectivityCheckingInterceptor(
private val connectivityManager: ConnectivityManager
) : Interceptor, ConnectivityManager.NetworkCallback() {
private var online = false
init {
if (Build.VERSION.SDK_INT >= 24) {
connectivityManager.registerDefaultNetworkCallback(this)
}
}
override fun intercept(chain: Interceptor.Chain): Response {
if (Build.VERSION.SDK_INT < 24) {
online = connectivityManager.activeNetworkInfo?.isConnected ?: false
}
if (online) {
return chain.proceed(chain.request())
} else {
throw IOException("Internet connection is unavailable")
}
}
override fun onCapabilitiesChanged(
network: Network,
capabilities: NetworkCapabilities
) {
online = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
}
}
Uasge example:
val client = OkHttpClient.Builder()
.addInterceptor(ConnectivityCheckingInterceptor(connectivityManager))
.build()
Conclusion
The most popular Android HTTP client isn’t aware of the network state by default and there is no single source of information about Internet capabilities of Android devices. Those are the main reasons why such a seemingly simple thing as figuring out if you’re online or not might turn out to be a bit more involved.