How to Add "No Internet Connection" in Android Studio (2026 Guide)

To add a "No Internet Connection" feature in Android Studio, get ConnectivityManager, read the active network's NetworkCapabilities, and confirm it has both NET_CAPABILITY_INTERNET and NET_CAPABILITY_VALIDATED. For live updates, register a NetworkCallback with registerDefaultNetworkCallback() and unregister it in onStop().
That second capability, VALIDATED, is the part almost every tutorial skips. Without it your app says "You're online" while a café Wi-Fi login page silently swallows every API call.
This guide gives you all four offline UIs — dialog, full-screen layout, Snackbar and Compose banner — in Kotlin and Java, with complete paste-ready files. Everything here works on Android 6.0 through Android 16.
In a hurry? Jump to: which offline UI to use · the 10-line check · No Internet dialog · full-screen layout · Compose banner · complete NetworkMonitor file · the captive portal trap · emulator has no internet? · 5 mistakes to avoid
Which "No Internet" UI should you use?
Pick the UI before you write code. Using a blocking dialog where a banner belongs is the fastest way to get one-star reviews.
| UI | Best for | Blocks the app? | Jump to |
|---|---|---|---|
| Snackbar | A failed action the user can retry | No | Snackbar code |
| Top banner | Apps that work offline with cached data | No | Compose banner |
| AlertDialog | Apps that cannot function at all offline | Yes | Dialog code |
| Full-screen layout | Splash / launch, WebView apps, login screens | Yes | Layout code |
First: what no longer works
If you copied code from an older tutorial, check this list before you debug anything else. Most "my code doesn't work" cases are this table.
| Old code | Status | Use instead |
|---|---|---|
getActiveNetworkInfo() | Deprecated in API 29 | activeNetwork + getNetworkCapabilities() |
NetworkInfo class | Deprecated in API 29 | NetworkCapabilities |
CONNECTIVITY_ACTION broadcast | Deprecated in API 28 | NetworkCallback |
Receiver declared in AndroidManifest.xml | Never fires since Android 7.0 (API 24) | registerDefaultNetworkCallback() |
Pinging 8.8.8.8 on the main thread | Crashes with NetworkOnMainThreadException | Coroutine + generate_204 |
Translation: your app is not broken. The tutorial is old.
Step 1: add the permissions
Put these in AndroidManifest.xml, above the <application> tag:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
- Both are normal permissions — no runtime pop-up, no user prompt.
ACCESS_NETWORK_STATElets you read the status.INTERNETlets you actually make the calls.- You do not need
ACCESS_WIFI_STATE. Old tutorials add it out of habit.
Step 2: the 10-line check (Kotlin)
Use this right before a one-off action — a login tap, a Retry button, a form submit.
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
fun Context.isOnline(): Boolean {
val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val caps = cm.getNetworkCapabilities(cm.activeNetwork) ?: return false
return caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
}
Call it anywhere with if (isOnline()) { ... }.
Why two capabilities and not one?
NET_CAPABILITY_INTERNET= "this network is supposed to reach the internet."NET_CAPABILITY_VALIDATED= "Android probed it and real traffic got through."
Only the second one tells you the truth. Skip it and every airport Wi-Fi, office guest network and dead router in the world reports your user as online.
The same check in Java
Still on Java? Create NetworkUtil.java and paste this whole file:
package com.example.myapp;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
public class NetworkUtil {
public static boolean isOnline(Context context) {
ConnectivityManager cm = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm == null) return false;
Network network = cm.getActiveNetwork();
if (network == null) return false;
NetworkCapabilities caps = cm.getNetworkCapabilities(network);
if (caps == null) return false;
return caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
&& caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
}
}
Use it from any Activity with NetworkUtil.isOnline(this).
What minSdk do you need?
| API you want | Minimum API level | Android version |
|---|---|---|
NetworkCapabilities | 21 | 5.0 Lollipop |
getActiveNetwork() | 23 | 6.0 Marshmallow |
NET_CAPABILITY_VALIDATED | 23 | 6.0 Marshmallow |
registerDefaultNetworkCallback() | 24 | 7.0 Nougat |
registerNetworkCallback(NetworkRequest) instead.Also Read: Android Snackbar: Complete Guide with Examples
Step 3: live updates with NetworkCallback
A one-time check goes stale the second the user walks out of Wi-Fi range. For a UI that appears and disappears on its own, register a callback.
class MainActivity : AppCompatActivity() {
private lateinit var cm: ConnectivityManager
private val callback = object : ConnectivityManager.NetworkCallback() {
override fun onCapabilitiesChanged(
network: Network,
caps: NetworkCapabilities
) {
val online = caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
runOnUiThread { showOfflineUi(!online) }
}
override fun onLost(network: Network) {
runOnUiThread { showOfflineUi(true) }
}
}
override fun onStart() {
super.onStart()
cm = getSystemService(ConnectivityManager::class.java)
cm.registerDefaultNetworkCallback(callback)
}
override fun onStop() {
super.onStop()
cm.unregisterNetworkCallback(callback) // never skip this
}
}
Three things worth knowing about this code:
- Callbacks run on a background thread. Touch a View without
runOnUiThreadand you get a crash, not a banner. - Android limits how many callbacks one app can register. Forget
unregisterNetworkCallback()and a rotating screen leaks its way to aTooManyRequestsException. - Read the capabilities handed to you, not
cm.getNetworkCapabilities()inside the callback. Calling back into the manager mid-callback is a documented race condition.
Step 4: the complete NetworkMonitor.kt (copy this)
This is the whole file. Create NetworkMonitor.kt, paste it, change the package name, and you're done.
package com.example.myapp
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
/** One-shot check. Use before a single action. */
fun Context.isOnline(): Boolean {
val cm = getSystemService(ConnectivityManager::class.java) ?: return false
val caps = cm.getNetworkCapabilities(cm.activeNetwork) ?: return false
return caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
}
/** Live stream. Emits true when online, false when offline. */
fun Context.connectivityFlow(): Flow<Boolean> = callbackFlow {
val cm = getSystemService(ConnectivityManager::class.java)
val cb = object : ConnectivityManager.NetworkCallback() {
override fun onCapabilitiesChanged(n: Network, c: NetworkCapabilities) {
trySend(c.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED))
}
override fun onLost(n: Network) { trySend(false) }
}
cm.registerDefaultNetworkCallback(cb)
trySend(isOnline()) // emit current state immediately
awaitClose { cm.unregisterNetworkCallback(cb) }
}.distinctUntilChanged()
awaitClose is the hero here — it unregisters automatically when the collector dies, so lifecycle leaks stop being your problem. distinctUntilChanged() kills the duplicate emissions you get when Wi-Fi and mobile data switch at the same moment.
Step 5: the "No Internet Connection" dialog
This is the classic version — a blocking pop-up with Retry and Exit. Use it when your app genuinely cannot work offline.
import com.google.android.material.dialog.MaterialAlertDialogBuilder
private var dialog: androidx.appcompat.app.AlertDialog? = null
private fun showNoInternetDialog() {
if (dialog?.isShowing == true) return // stops duplicate dialogs
dialog = MaterialAlertDialogBuilder(this)
.setTitle("No Internet Connection")
.setMessage("You are not connected to the internet. Check your Wi-Fi or mobile data and try again.")
.setCancelable(false)
.setPositiveButton("Retry") { d, _ ->
d.dismiss()
if (isOnline()) loadData() else showNoInternetDialog()
}
.setNegativeButton("Exit") { _, _ -> finish() }
.show()
}
Dismiss it automatically when the network returns:
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
connectivityFlow().collect { online ->
if (online) { dialog?.dismiss(); loadData() }
else showNoInternetDialog()
}
}
}
Two rules for dialogs:
- Always guard with
isShowing. Without it, a flapping network stacks five dialogs on top of each other. - Always dismiss it yourself when connection returns. Making the user tap Retry on a working connection feels broken.
Step 6: the full-screen "No Internet" layout
Best for splash screens, login screens and WebView apps. Create layout/no_internet.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/noInternetView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="24dp"
android:visibility="gone">
<ImageView
android:layout_width="120dp"
android:layout_height="120dp"
android:src="@drawable/ic_wifi_off"
android:contentDescription="No internet connection" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="No Internet Connection"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginTop="16dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Check your Wi-Fi or mobile data and try again."
android:gravity="center"
android:layout_marginTop="8dp" />
<Button
android:id="@+id/btnRetry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Retry"
android:layout_marginTop="24dp" />
</LinearLayout>
Include it in your main layout, then flip it on and off:
<include layout="@layout/no_internet" />
private fun showOfflineUi(offline: Boolean) {
binding.noInternetView.isVisible = offline
binding.contentView.isVisible = !offline
}
binding.btnRetry.setOnClickListener {
if (isOnline()) loadData() else toast("Still offline")
}
Get a free ic_wifi_off icon from File → New → Vector Asset in Android Studio. No download needed.
Step 7: the offline banner in Jetpack Compose
In your ViewModel:
val isOnline = context.connectivityFlow()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), true)
In your screen:
val online by viewModel.isOnline.collectAsStateWithLifecycle()
AnimatedVisibility(visible = !online) {
Surface(color = MaterialTheme.colorScheme.errorContainer) {
Row(
Modifier.fillMaxWidth().padding(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Default.WifiOff, contentDescription = null)
Spacer(Modifier.width(8.dp))
Text("You're offline. Showing saved content.")
Spacer(Modifier.weight(1f))
TextButton(onClick = viewModel::retry) { Text("Retry") }
}
}
}
Step 8: the Snackbar version (one line)
Snackbar.make(rootView, "You're offline", Snackbar.LENGTH_INDEFINITE)
.setAction("Retry") { viewModel.retry() }
.show()
Use LENGTH_INDEFINITE with an action, not LENGTH_LONG. A message that vanishes in two seconds helps nobody.
Handling no internet in a WebView app
WebView apps are the most common reason people search for this feature — and they need extra handling, because WebView shows its own ugly "net::ERR_INTERNET_DISCONNECTED" page by default.
webView.webViewClient = object : WebViewClient() {
override fun onReceivedError(
view: WebView,
request: WebResourceRequest,
error: WebResourceError
) {
if (request.isForMainFrame) {
view.loadUrl("about:blank") // hide the default error page
showOfflineUi(true) // show your own layout
}
}
override fun onPageFinished(view: WebView, url: String) {
if (url != "about:blank") showOfflineUi(false)
}
}
Two extras that make offline WebView apps feel professional:
- Turn on caching so the last page still loads:
webView.settings.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORKwhen offline. - Reload with
webView.reload()from your Retry button, notloadUrl()— it keeps the user's place.
The captive portal trap (the bug nobody warns you about)
Picture a user on a café hotspot who never tapped "Accept terms."
- The phone shows full Wi-Fi bars.
NET_CAPABILITY_INTERNETreturns true.- Every API call comes back as the café's login HTML, not your JSON.
- Your app shows a blank screen and a parsing error nobody can explain.
Two lines fix it:
- Require
NET_CAPABILITY_VALIDATED— Android already probed the network for you. - Detect
NET_CAPABILITY_CAPTIVE_PORTALand tell the user to sign in, instead of showing a generic error.
val portal = caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL)
if (portal) showMessage("Sign in to this Wi-Fi network to continue")
This is the single biggest difference between a tutorial-grade offline check and a production-grade one.
Do you still need a real ping?
Usually no. VALIDATED covers about 95% of cases. Add a real probe only if your users sit on captive-heavy networks (campus, hospital, corporate VPN) or your app must be certain before a payment step.
suspend fun hasRealInternet(): Boolean = withContext(Dispatchers.IO) {
runCatching {
val conn = URL("https://clients3.google.com/generate_204")
.openConnection() as HttpURLConnection
conn.connectTimeout = 1500
conn.readTimeout = 1500
conn.connect()
conn.responseCode == 204
}.getOrDefault(false)
}
Why generate_204? It returns an empty body, so it is fast and cheap — and a captive portal answers with 200 and HTML instead of 204, which instantly exposes the trap. Never run it on the main thread.
Don't just say "No Internet" — do something
A red screen that only blocks the user is a bad screen. Rank your response like this:
- Show cached data first. Room or DataStore for text, Coil or Glide for images. Offline should feel like a stale app, not a dead one.
- Keep one clear action. One Retry button beats three vague options.
- Auto-recover. With the Flow above, the offline UI disappears by itself. Users should never have to restart your app.
- Queue writes instead of dropping them. Use
WorkManagerwith aNetworkType.CONNECTEDconstraint so that comment, order or upload sends itself later. - Say what happened, not what failed. "You're offline. Showing saved content." beats "Error: Unable to resolve host."
val request = OneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
).build()
WorkManager.getInstance(context).enqueue(request)
How to test it in 60 seconds
You cannot ship what you have not seen fail. Run these from the Terminal tab in Android Studio.
| What to test | How |
|---|---|
| Full offline | Emulator → Extended Controls (…) → Cellular → Data status: Denied, plus Wi-Fi off |
| Airplane mode | adb shell cmd connectivity airplane-mode enable |
| Wi-Fi off only | adb shell svc wifi disable |
| Mobile data off only | adb shell svc data disable |
| Slow / flaky network | Extended Controls → Cellular → Network type: EDGE |
| Captive portal | Connect a real device to any public Wi-Fi and don't log in |
Checklist while testing:
- The offline UI appears within a second of losing the network.
- It disappears on its own when the network returns.
- Rotating the screen 10 times does not crash or stack duplicate dialogs.
- Background → foreground still shows the correct state.
Bonus: what if your emulator has no internet?
Half the people who search this phrase aren't building a feature at all — their emulator itself is offline and Gradle or their app can't reach the network. Work down this list:
- Check the Wi-Fi name. The emulator connects to a fake network called AndroidWifi. If it isn't there, the emulator's networking failed to start.
- Turn off airplane mode inside the emulator. It sticks between sessions and is the most common cause.
- Cold boot. Device Manager → the dropdown next to your AVD → Cold Boot Now. This fixes stale snapshot networking.
- Fix DNS. Launch with your own DNS:
emulator -avd Pixel_8 -dns-server 8.8.8.8. - Check your host VPN. A VPN or corporate proxy on your computer regularly breaks the emulator's NAT. Disconnect and retest.
- Check Android Studio's proxy. Settings → Appearance & Behavior → System Settings → HTTP Proxy → set to No proxy (or Auto-detect).
- Confirm from the shell:
adb shell ping -c 3 8.8.8.8. If that fails, the problem is the emulator, not your code. - Wipe data as a last resort. Device Manager → Wipe Data, or create a fresh AVD.
5 mistakes I see in almost every project
- Checking only
NET_CAPABILITY_INTERNET. Your app lies to the user on every captive network. - Registering the callback in
onCreate()and never unregistering. Leak, thenTooManyRequestsExceptionin production. - Updating UI directly from the callback. It's a background thread — use
runOnUiThread, a Flow, orLiveData.postValue(). - Blocking the entire app when offline. Cache first, block only the actions that truly need the network.
- Checking connectivity instead of handling errors. Connectivity can drop mid-request. Keep your
try/catcharound Retrofit calls anyway — the check is a UX improvement, not a replacement for error handling.
Bottom line
Adding "No Internet Connection" in Android Studio comes down to four moves:
- Add
ACCESS_NETWORK_STATEandINTERNET. - Check
NET_CAPABILITY_INTERNETandNET_CAPABILITY_VALIDATED. - Use
registerDefaultNetworkCallback()(wrapped in a Flow) for live updates, and always unregister. - Pick the right UI — banner for cached apps, dialog or full screen only when there's truly nothing to show.
Copy the NetworkMonitor.kt file above into your project today, wire in whichever UI fits your app, then turn on airplane mode and watch it work. That's a 15-minute change your users will feel every single day.
Frequently Asked Questions
How do I check for an internet connection in Android Studio?
Get ConnectivityManager, read getNetworkCapabilities(activeNetwork), and confirm the network has both NET_CAPABILITY_INTERNET and NET_CAPABILITY_VALIDATED. Add the ACCESS_NETWORK_STATE and INTERNET permissions to AndroidManifest.xml. Both are normal permissions, so there is no runtime prompt.
How do I show a "No Internet Connection" dialog in Android Studio?
Build a MaterialAlertDialogBuilder with the title "No Internet Connection", call setCancelable(false), and add a Retry button that re-checks connectivity plus an Exit button that calls finish(). Guard it with a dialog.isShowing check so a flapping network does not stack multiple dialogs, and dismiss it automatically when the connection returns.
Why is getActiveNetworkInfo() showing as deprecated?
getActiveNetworkInfo() and the NetworkInfo class were deprecated in Android 10 (API 29). The replacement is getActiveNetwork() plus getNetworkCapabilities(). The old code may still compile, but it cannot describe modern networks correctly and should not be used in new projects.
What is the difference between NET_CAPABILITY_INTERNET and NET_CAPABILITY_VALIDATED?
NET_CAPABILITY_INTERNET only means the network is set up to reach the internet. NET_CAPABILITY_VALIDATED means Android actually probed it and real traffic got through. A café or airport Wi-Fi with a login page has INTERNET but not VALIDATED, so checking only the first one makes your app report users as online when nothing works.
Why does my BroadcastReceiver for CONNECTIVITY_CHANGE never fire?
Apps targeting Android 7.0 (API 24) and above no longer receive the CONNECTIVITY_ACTION broadcast through a receiver declared in AndroidManifest.xml, and the broadcast itself was deprecated in API 28. Use ConnectivityManager.registerDefaultNetworkCallback() instead and unregister it when your screen stops.
Should I use a dialog, a Snackbar or a full-screen layout for no internet?
Use a Snackbar for a single failed action the user can retry, a top banner for apps that still work with cached data, an AlertDialog when the app cannot function at all offline, and a full-screen layout for splash screens, login screens and WebView apps. Block the screen only when there is genuinely nothing to show.
How do I show a "No Internet Connection" page in a WebView app?
Override onReceivedError in your WebViewClient, check request.isForMainFrame, load about:blank to hide WebView’s default error page, and show your own no_internet layout instead. Restore the content in onPageFinished, and call webView.reload() from the Retry button so the user keeps their place.
How do I test the no internet feature in the emulator?
Open Extended Controls in the emulator and set Cellular data status to Denied with Wi-Fi off, or run adb shell cmd connectivity airplane-mode enable. Use adb shell svc wifi disable and adb shell svc data disable to test each radio separately, and set network type to EDGE to test a slow connection. Also test on a real device, since emulator networking is virtualized.
Why does my Android Studio emulator have no internet?
Check that airplane mode is off inside the emulator and that it is connected to the AndroidWifi network, then try Cold Boot Now from Device Manager. If it still fails, launch with emulator -avd YourAvd -dns-server 8.8.8.8, disconnect any VPN or proxy on your computer, and set Android Studio HTTP Proxy to No proxy. Run adb shell ping -c 3 8.8.8.8 to confirm whether the problem is the emulator or your code.
What minimum SDK version do I need for this code?
NetworkCapabilities requires API 21, getActiveNetwork() and NET_CAPABILITY_VALIDATED require API 23, and registerDefaultNetworkCallback() requires API 24. Since Android Studio now defaults to a minSdk above 24, all of the code works with no version checks. Below API 24, use registerNetworkCallback() with a NetworkRequest instead.
Do I still need try/catch around my API calls if I check connectivity?
Yes. A connectivity check improves the user experience, but the network can drop in the middle of a request and a server can fail while the connection is fine. Keep error handling around your Retrofit or OkHttp calls and treat the connectivity check as an extra layer, not a replacement.





