Detect Internet Connection in Flutter

Video thumbnail

Checking if a Flutter app has an internet connection seems simple... until you start integrating navigation, widget lifecycle, and UI updates. I've been through that pain several times, and here I'm going to tell you exactly how I solve it in my projects using `connectivity_plus`, a reusable CustomScaffold, and a flow that doesn't break the user experience.

Why is it Important to Verify the Internet Connection in Flutter?

A large part of modern apps depend on the internet: lists of posts, authentication, images, feeds, etc. If the app tries to build an interface that needs data without having internet, the loading won't just fail: the navigation flow can also break or incomplete screens may be presented.

In my case, I usually work with applications where every page needs to make a request. That's why I always prefer to validate the connection before building the UI. And no, Flutter doesn't have a native way to "stop the build until we know if there's internet," so we have to do it intelligently.

Available Options for Detecting Connectivity in Flutter

Before showing you my approach, it's worth reviewing what alternatives exist.

Quick Comparison: `connectivity_plus` vs. `internet_connection_checker`

`connectivity_plus`:

  • Detects if you are connected to some type of network (WiFi, mobile, ethernet).
  • Does not guarantee real internet, only that a network connection exists.
  • Very useful for initial flows and integration with the widget lifecycle.

`internet_connection_checker`:

  • Checks if you can actually "go out" to the internet.
  • Pings servers to validate real outbound connectivity.
  • Useful when you need absolute certainty of connectivity.

When to Use Each Package

I use `connectivity_plus` when I need to quickly know if there is a network to show or block screens. In contrast, I use `internet_connection_checker` when I need to confirm real connectivity before consuming an API.

In this article, we focus on `connectivity_plus`, because it's ideal for controlling entire screens, such as a "no connection" screen.

How to Verify Internet Connection with `connectivity_plus`

I'll show you how you can verify the internet connection in a Flutter application. For this, I am using the following package:

import 'package:connectivity_plus/connectivity_plus.dart';

It is installed using:

$ flutter pub add connectivity_plus

You can see the compatibility it has here. It's a fairly similar process as always: you open `pubspec.yaml`, go to the installation part, copy the package name, and specify the version if you want; otherwise, you just don't include it.

Once installed, you import it wherever you want to perform the verification. In my case, I'm using a personalized Scaffold which I call CustomScaffold.

Usage of the CustomScaffold

I use this widget to place it on all my pages, that is, on all the screens I have for my application (all the ones you can see here). All the pages I have routed at the application level use this CustomScaffold.

In addition to being able to, for example, customize where I want to place the drawer and so on, it is also useful for this type of situation in which we want to perform a preliminary check before loading each page, as in this case would be the internet connection verification.

Although that implementation depends somewhat on where you want to place it, the point is that, in the end, this is just a widget like any other: a `StatefulWidget`.

Detecting Connectivity in `initState` Without Breaking the Lifecycle

When I started implementing this, one of the first things I discovered is that it's not a good idea to put `async` directly into `initState()`, even if Flutter lets you. I've seen strange behaviors in widget mounting and prefer to avoid it.

So what I do is invoke a separate method, which *is* `async`, from `initState()`:

@override
void initState() {
 init();
 super.initState();
}

This keeps the lifecycle clean and leaves the heavy lifting out.

Creating a Safe Asynchronous Method to Validate Internet

My method usually looks like this:

Future<bool> checkIsInternetConnection() async {
 var connectivityResult = await Connectivity().checkConnectivity();
 return connectivityResult != ConnectivityResult.none;
}

The operation is simple:

  • A boolean is defined, which is `true` by default. This indicates that, initially, we assume there is an internet connection, which is the safest assumption.
  • Then, the connection is verified using an `async` method (`await`), as you can see.

The result of this method can return several things, but for our practical purposes, we are only interested in whether there is no connection.

  • If there is no connection, the method returns a `ConnectivityResult.none` object.
  • If there *is* a connection, we could also identify if it's Ethernet, mobile, or WiFi, but again, for this example, the important thing is detecting when we have no connection.

Here we check if the value is equal to a certain state.

  • If the condition is met, it means we don't have an internet connection.
  • In that case, the method would return `false`, indicating the absence of connection.
  • With this result, we can execute the corresponding action when the internet is unavailable.

Internet Connection Verification

So, I'm not verifying this directly here for now; later I'll indicate why.

Remember that the main problem is that this process is difficult to handle asynchronously, because even though Flutter accepts it, several issues can arise.

That's why I moved the asynchronous part to another method. Here I simply invoke it, and note that I am not blocking the main thread. We'll see how to handle this correctly later.

Asynchronous Verification Method

Here we have the asynchronous method that we are invoking. This method can be used for any initialization, but in this case, we only need to check the internet connection.

In my flow, if `_thereIsInternet` is false, then in `build()` I return a full screen that basically says "no internet" and offers a button to retry.

Something like this:

if (!_thereIsInternet) {
 return Scaffold(
   appBar: AppBar(title: Text(widget.title)),
   body: Column(
     mainAxisAlignment: MainAxisAlignment.center,
     children: [
       Text('No hay conexión a internet'),
       SizedBox(height: 15),
       MaterialButton(
         color: Theme.of(context).primaryColor,
         onPressed: () async {
           await init();
           if (_thereIsInternet) {
             Navigator.pushReplacement(
               context,
               MaterialPageRoute(builder: (_) => ListPage(...)),
             );
           }
         },
         child: Text('Reintentar', style: TextStyle(color: Colors.white)),
       )
     ],
   ),
 );
}

Finally, we check the response and act accordingly.

ConnectivityResult

For some reason, I implemented it in reverse. It could perfectly be done in the traditional way, but in this case, I left it like this:

return  connectivityResult != ConnectivityResult.none

As you might guess, and as seen in the code, this is an asynchronous request. All connections involving disk or Internet are asynchronous, for obvious reasons.

  • If there is a connection, the normal widget is built.
  • If there is no connection, we can display another type of widget, such as an error message or a network failure indicator.

Complete example integrating it into a reusable Scaffold

This is where I really take advantage of the pattern.

How a CustomScaffold Works to Validate Internet on Every Page

I have a widget that I use on all pages: a CustomScaffold.
I created it because I needed a common point to:

  • customize appbars,
  • handle drawers,
  • and validate the connection before building the real screen.

It's perfect because all my pages go through there, so the verification works automatically.

@override
 Widget build(BuildContext context) {
   if (!_thereIsInternet) {
     // no hay conexion
     return Scaffold(
       appBar: AppBar(
         title: Text(widget.title),
       ),
       body: SizedBox(
         width: double.infinity,
         child: Column(
           mainAxisAlignment: MainAxisAlignment.center,
           crossAxisAlignment: CrossAxisAlignment.center,
           children: [
             Text(LocaleKeys.thereIsNoInternet.tr()),
             const SizedBox(height: 15),
             MaterialButton(
               color: Theme.of(context).primaryColor,
               textTheme: Theme.of(context).buttonTheme.textTheme,
               // textTheme: Theme.of(context).buttonTheme,
               onPressed: () async {
                 await init();
                 if (_thereIsInternet) {
                   // navega a alguna pagina para regargar
                   Navigator.pushReplacement(
                       context,
                       MaterialPageRoute(
                           builder: (context) => ListPage(
                               false, 0, 0, '', LocaleKeys.lastsPosts.tr())));
                   // setState(() {});
                   // no llama al metodo de build
                   // Navigator.pushReplacement(
                   //     context,
                   //     MaterialPageRoute(
                   //         builder: (BuildContext context) => super.widget));
                 }
               },
               child: Text(LocaleKeys.reload.tr(),
                   style: Theme.of(context)
                       .textTheme
                       .bodyLarge!
                       .copyWith(color: Colors.white)),
             ),
           ],
         ),
       ),
     );
   }

In this case, I use a button in the middle that indicates there is no internet, which is the message I am currently displaying.

When the user wants to check the connection, I place a button that calls the verification method I showed you before.

If the verification determines that `_thereIsInternet` is `true`, meaning there is an internet connection, then I can:

  • Show additional content.
  • Navigate to a specific page, in this case, the post listing.

This button will always be present, and every time the user taps it, the connection check runs. If the connection is successful (`true`), the application navigates correctly to the other page.

From the user's perspective, this makes it seem like the application is reloading. Ideally, it would be better to reload the current page, but as far as I have investigated, there is no official method in Flutter to do a "hard reload" of the page.

To circumvent this limitation, I use `pushReplacement` to navigate to another page, replacing the current one.

Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => ListPage( false, 0, 0, '', LocaleKeys.lastsPosts.tr())));

Therefore, we replace this page and avoid all the complexity related to navigation and internet verification.

Not much more to say: it's that simple. With these lines of code, you can easily verify when there is a connection or not and, from there, act accordingly.

  • If there is no connection, you can show a different interface.
  • In this case, for example, if it is the post listing page, nothing will be built because there is no connection.
  • Once we have a connection, we simply navigate to a specific page.

Displaying an Offline Screen

The `CustomScaffold` checks the connection in `initState`, and if there's no internet:

  • It doesn't display the page's actual content.
  • It displays the "no connection" screen.
  • It waits for the user to tap Retry.

Reloading and Navigating When Internet Returns (`pushReplacement`)

Flutter doesn't have a "hard reload" for pages.
I investigated this extensively myself and couldn't find an official way to reload the entire page "from within."

So I use this technique:

Navigator.pushReplacement(
 context,
 MaterialPageRoute(builder: (context) => ListPage(...)),
);

This reconstructs the navigation starting from that page and saves you a lot of headaches.

Best Practices for Handling Offline Mode in Flutter Apps

Recommended UX Patterns

  • Don't block the user without explanation.
  • Always offer a clear "Retry" button.
  • Avoid empty screens, infinite loaders, or plain errors.
  • Allow navigation to sections that don't need internet.
  • Give immediate feedback when tapping the retry button.

Common Errors When Using `async`/`await` in Widgets

  • Putting `async` directly into `initState`.
  • Changing state variables without calling `setState` afterward.
  • Navigating from `build()` (this breaks animations).
  • Creating listeners without canceling them.
  • Assuming that “having a network = having internet.”

Frequently Asked Questions about Connectivity in Flutter

  • Does `connectivity_plus` check for real internet?
    • No. It only detects if you have a network connection.
  • How do I check for real internet?
    • With `internet_connection_checker`.
  • Can an entire page be reloaded in Flutter?
    • Not natively. The closest is using `pushReplacement`.
  • Is it mandatory to use a custom Scaffold?
    • No, but it helps a lot if you need repeated logic.

Conclusion

Checking the internet connection in Flutter is not complicated, but doing it correctly—without blocking the lifecycle, without breaking the UI, and without filling the code with hacks—requires an ordered approach.

The combination I use in my projects:

  • ✔️ `connectivity_plus`
  • ✔️ a safe async method
  • ✔️ a `CustomScaffold` that centralizes the check
  • ✔️ clean offline screens
  • ✔️ `pushReplacement` to reconstruct navigation

I agree to receive announcements of interest about this Blog.

We will see how we can detect Internet connection in a Flutter application using a package.

| 👤 Andrés Cruz

🇪🇸 En español