Stay Connect: Internet change listener in Flutter

Abdul Rehman
5 min readApr 16, 2023

--

In this blog, we will build an Internet change listener to gracefully handle changes in connectivity in our Flutter app.

There are many scenarios where we want users to stop using our app if there is no internet connection. We might want to optimize our app from unwanted flow and error caused due to network changes. The best way to handle all this scenario is by listening for network changes and showing a separate screen which will persist until the user has access to internet.

Import the Plugin πŸ”§

We will use connectivity_plus plugin to listen for changes in connection status. Add the below code in your project pubspec.yaml

πŸ”— Read more β†’ https://pub.dev/packages/connectivity_plus

dependencies:
flutter:
sdk: flutter

connectivity_plus: ^3.0.4

Create our Singleton class πŸ’»

We will implement a Singleton class to handle all our connection changes. Let’s create a base Singleton class ConnectionStatusListener.

class ConnectionStatusListener {

//This creates the single instance by calling the `_internal` constructor specified below
static final _singleton = ConnectionStatusListener._internal();

ConnectionStatusListener._internal();

//This is what's used to retrieve the instance through the app
static ConnectionStatusListener getInstance() => _singleton;

}

Now we will implement a method which will check if our device is connected to internet or not. We will access google.com to find out if our device is connected to internet.

We will use Stream to handle changes in our connection state.

πŸ”— Read more about stream β†’ https://api.dart.dev/stable/2.19.6/dart-async/StreamController-class.html

import 'dart:io';

class ConnectionStatusListener {

//This tracks the current connection status
bool hasConnection = false;

//This is how we'll allow subscribing to connection changes
StreamController connectionChangeController = StreamController.broadcast();

Stream get connectionChange => connectionChangeController.stream;


//The test to actually see if there is a connection
Future<bool> checkConnection() async {
bool previousConnection = hasConnection;

try {
final result = await InternetAddress.lookup('google.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
hasConnection = true;
} else {
hasConnection = false;
}
} on SocketException catch (_) {
hasConnection = false;
}

//The connection status changed send out an update to all listeners
if (previousConnection != hasConnection) {
connectionChangeController.add(hasConnection);
}

return hasConnection;
}


//A clean up method to close our StreamController
//Because this is meant to exist through the entire application life cycle this isn't really an issue
void dispose() {
connectionChangeController.close();
}
}

Add Connectivity Listener πŸ”Œ

Now we will add connection change listener from our plugin mentioned above. This listener will notify us of any changes in network change like turning off data/Wi-Fi, no internet, or any other change in network status.

import 'dart:async';
import 'dart:io';

import 'package:connectivity_plus/connectivity_plus.dart';

class ConnectionStatusListener {

//connectivity_plus
final Connectivity _connectivity = Connectivity();

//flutter_connectivity's listener
void _connectionChange(ConnectivityResult result) {
checkConnection();
}


//Hook into connectivity_plus's Stream to listen for changes
//And check the connection status out of the gate
Future<void> initialize() async {
_connectivity.onConnectivityChanged.listen(_connectionChange);
await checkConnection();
}

}

Initialize our Listener πŸ‘‚

Now we will initialize our listener in the project main class. This will allow us to listen any changes in network as soon as our app is running.


//Intialize our singleton class for listening to changes.
initNoInternetListener() async {
var connectionStatus = ConnectionStatusListener.getInstance();
await connectionStatus.initialize();

//We are checking initial status here. This will handle our app state when
//it is started in no internet state.
if (!connectionStatus.hasConnection) {
updateConnectivity(false, connectionStatus);
}

//This callback will give us any changes in network
connectionStatus.connectionChange.listen((event) {
print("initNoInternetListener $event");
updateConnectivity(event, connectionStatus);
});
}

updateConnectivity(
dynamic hasConnection,
ConnectionStatusListener connectionStatus,
) {
if (!hasConnection) {
connectionStatus.hasShownNoInternet = true;
//Handle no internet here

Navigator.push(
context,
MaterialPageRoute(builder: (context) => const NoInternetScreen()),
);

} else {
if (connectionStatus.hasShownNoInternet) {
connectionStatus.hasShownNoInternet = false;
//Handle internet is resumed here

Navigator.pop(context);
}
}
}


//Adding our listener here
void main() {

runApp(const MyApp());
initNoInternetListener();

}

Add Screen for No Internet 🚫

Let create our StatelessWidget that will be shown when there is no internet. You can customize it according to your need, sky is the limit here. We will disable back navigation from this screen using WillPopScope widget. With the help of WillPopScope we will forcefully persist NoInternetScreen until connection is resumed.

import 'package:blogs/generated/assets.dart';
import 'package:flutter/material.dart';

class NoInternetScreen extends StatelessWidget {
const NoInternetScreen({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Image.asset(Assets.assetsUndrawServerDown),
),
const SizedBox(height: 80),
const Text(
'No Internet Connection',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w800,
fontSize: 18,
),
),
const SizedBox(height: 20),
const Text(
'No internet connection found. Check your \n connection or try again!',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w400,
fontSize: 14,
),
),
const SizedBox(height: 50),
],
),
),
),
),
);
}
}

Entire code πŸ”

Entire codebase for our Connection Singleton class.

import 'dart:async';
import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';

class ConnectionStatusListener {
//This creates the single instance by calling the `_internal` constructor specified below
static final _singleton = ConnectionStatusListener._internal();

ConnectionStatusListener._internal();

bool hasShownNoInternet = false;

//connectivity_plus
final Connectivity _connectivity = Connectivity();

//This is what's used to retrieve the instance through the app
static ConnectionStatusListener getInstance() => _singleton;

//This tracks the current connection status
bool hasConnection = false;

//This is how we'll allow subscribing to connection changes
StreamController connectionChangeController = StreamController.broadcast();

Stream get connectionChange => connectionChangeController.stream;

//flutter_connectivity's listener
void _connectionChange(ConnectivityResult result) {
checkConnection();
}

//The test to actually see if there is a connection
Future<bool> checkConnection() async {
bool previousConnection = hasConnection;

try {
final result = await InternetAddress.lookup('google.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
hasConnection = true;
} else {
hasConnection = false;
}
} on SocketException catch (_) {
hasConnection = false;
}

//The connection status changed send out an update to all listeners
if (previousConnection != hasConnection) {
connectionChangeController.add(hasConnection);
}

return hasConnection;
}

//Hook into connectivity_plus's Stream to listen for changes
//And check the connection status out of the gate
Future<void> initialize() async {
_connectivity.onConnectivityChanged.listen(_connectionChange);
await checkConnection();
}

//A clean up method to close our StreamController
//Because this is meant to exist through the entire application life cycle this isn't really an issue
void dispose() {
connectionChangeController.close();
}
}

updateConnectivity(
dynamic hasConnection,
ConnectionStatusListener connectionStatus,
) {
if (!hasConnection) {
connectionStatus.hasShownNoInternet = true;
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const NoInternetScreen()),
);
} else {
if (connectionStatus.hasShownNoInternet) {
connectionStatus.hasShownNoInternet = false;
Navigator.pop(context);
}
}
}

initNoInternetListener() async {
var connectionStatus = ConnectionStatusListener.getInstance();
await connectionStatus.initialize();
if (!connectionStatus.hasConnection) {
updateConnectivity(false, connectionStatus);
}
connectionStatus.connectionChange.listen((event) {
print("initNoInternetListener $event");
updateConnectivity(event, connectionStatus);
});
}

Thank you, and I hope this article helped you in some way. If you like the post, please support the article by sharing it. Have a nice day!πŸ‘‹

I am an mobile enthusiast πŸ“±. My expertise is in Flutter and Android development, with more than 5 years of experience in the industry.

I would love to write technical blogs for you. Writing blogs and helping people is what I crave for 😊 .You can contact me at my email(abdulrehman0796@gmail.com) for any collaboration πŸ‘‹

πŸ”— LinkedIn: https://www.linkedin.com/in/abdul-rehman-khilji/

--

--

Abdul Rehman

Mobile developer in love with Flutter and Android β™₯️