Advertisement
Featured

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

Aditya SinghUpdated
No Internet Connection

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.

UIBest forBlocks the app?Jump to
SnackbarA failed action the user can retryNoSnackbar code
Top bannerApps that work offline with cached dataNoCompose banner
AlertDialogApps that cannot function at all offlineYesDialog code
Full-screen layoutSplash / launch, WebView apps, login screensYesLayout code
Rule of thumb: block the screen only when there is genuinely nothing to show. Otherwise show cached content plus a banner.

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 codeStatusUse instead
getActiveNetworkInfo()Deprecated in API 29activeNetwork + getNetworkCapabilities()
NetworkInfo classDeprecated in API 29NetworkCapabilities
CONNECTIVITY_ACTION broadcastDeprecated in API 28NetworkCallback
Receiver declared in AndroidManifest.xmlNever fires since Android 7.0 (API 24)registerDefaultNetworkCallback()
Pinging 8.8.8.8 on the main threadCrashes with NetworkOnMainThreadExceptionCoroutine + generate_204
Android replaced this API twice. A manifest-registered connectivity receiver has not worked for apps targeting Android 7.0 and above.

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_STATE lets you read the status.
  • INTERNET lets 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 wantMinimum API levelAndroid version
NetworkCapabilities215.0 Lollipop
getActiveNetwork()236.0 Marshmallow
NET_CAPABILITY_VALIDATED236.0 Marshmallow
registerDefaultNetworkCallback()247.0 Nougat
Android Studio's default minSdk is well above 24 today, so all the code here works with no version checks. If you support below 24, use 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 runOnUiThread and 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 a TooManyRequestsException.
  • Read the capabilities handed to you, not cm.getNetworkCapabilities() inside the callback. Calling back into the manager mid-callback is a documented race condition.
Advertisement

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_NETWORK when offline.
  • Reload with webView.reload() from your Retry button, not loadUrl() — it keeps the user's place.

Also Read: Android SQLite Database Tutorial with Example

Advertisement

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_INTERNET returns 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_PORTAL and 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:

  1. 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.
  2. Keep one clear action. One Retry button beats three vague options.
  3. Auto-recover. With the Flow above, the offline UI disappears by itself. Users should never have to restart your app.
  4. Queue writes instead of dropping them. Use WorkManager with a NetworkType.CONNECTED constraint so that comment, order or upload sends itself later.
  5. 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 testHow
Full offlineEmulator → Extended Controls (…) → Cellular → Data status: Denied, plus Wi-Fi off
Airplane modeadb shell cmd connectivity airplane-mode enable
Wi-Fi off onlyadb shell svc wifi disable
Mobile data off onlyadb shell svc data disable
Slow / flaky networkExtended Controls → Cellular → Network type: EDGE
Captive portalConnect a real device to any public Wi-Fi and don't log in
Test on a real device too. Emulator networking is a virtual NAT and does not behave exactly like a phone switching towers.

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:

  1. 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.
  2. Turn off airplane mode inside the emulator. It sticks between sessions and is the most common cause.
  3. Cold boot. Device Manager → the dropdown next to your AVD → Cold Boot Now. This fixes stale snapshot networking.
  4. Fix DNS. Launch with your own DNS: emulator -avd Pixel_8 -dns-server 8.8.8.8.
  5. Check your host VPN. A VPN or corporate proxy on your computer regularly breaks the emulator's NAT. Disconnect and retest.
  6. Check Android Studio's proxy. Settings → Appearance & Behavior → System Settings → HTTP Proxy → set to No proxy (or Auto-detect).
  7. Confirm from the shell: adb shell ping -c 3 8.8.8.8. If that fails, the problem is the emulator, not your code.
  8. Wipe data as a last resort. Device Manager → Wipe Data, or create a fresh AVD.

5 mistakes I see in almost every project

  1. Checking only NET_CAPABILITY_INTERNET. Your app lies to the user on every captive network.
  2. Registering the callback in onCreate() and never unregistering. Leak, then TooManyRequestsException in production.
  3. Updating UI directly from the callback. It's a background thread — use runOnUiThread, a Flow, or LiveData.postValue().
  4. Blocking the entire app when offline. Cache first, block only the actions that truly need the network.
  5. Checking connectivity instead of handling errors. Connectivity can drop mid-request. Keep your try/catch around 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_STATE and INTERNET.
  • Check NET_CAPABILITY_INTERNET and NET_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.

Android DevelopmentAndroid StudioKotlinTutorial

Related Articles

Advertisement