Building an Always Listening Internet Connection Checker in Flutter using BLoC

Building an Always Listening Internet Connection Checker in Flutter using BLoC

Introduction

In mobile applications, maintaining a reliable connection to the internet is crucial. Flutter developers often need to ensure that the app responds appropriately when the network status changes.

In this article, we'll explore how to build a robust network connectivity checker in Flutter using connectivity_plus, internet_connection_checker, a network call to Google URL(https://www.google.com), debounce to limit the rate of function call, dependency injection with getIt and injectable, and state management using BLoc combined with freezed and of course Stream for the always-listening feature.

1. Setting Up Dependency Injection with getIt and injectable

What is getIt?

getIt is a simple service locator for Dart and Flutter that helps manage your application's dependencies. It allows you to register and retrieve instances of classes, ensuring that your dependencies are managed consistently across your application.

What is injectable?

injectable is a code generation package that works with getIt to automatically generate the necessary boilerplate code for dependency injection. It scans your project for classes marked with @injectable and registers them with getIt.

Step 1: Create a Flutter Project

Begin by creating a new Flutter project using the following command:

flutter create my_injectable_project
cd my_injectable_project

Setting Up Dependency Injection

To start, you'll need to set up getIt and injectable in your project. First, add the necessary dependencies to your pubspec.yaml file:

dependencies:
  freezed_annotation: ^2.4.1
  rxdart: ^0.28.0
  get_it: ^7.6.7
  injectable: ^2.3.2
  internet_connection_checker: ^1.0.0+1
  connectivity_plus: ^5.0.2
  fluttertoast: ^8.2.4
  flutter_bloc: ^8.1.3
  http: ^0.13.3

dev_dependencies:
  build_runner:
  freezed: ^2.4.7
  injectable_generator: ^2.4.1

Next, create an injection.dart file to configure getIt:

import 'package:get_it/get_it.dart';
import 'package:injectable/injectable.dart';
import 'package:app/core/dependency_injection/injection.config.dart';

final GetIt getIt = GetIt.instance;

@injectableInit
void configureDependencies(String env) => getIt.init(environment: env);

This file defines a getIt instance and a configureDependencies function that initializes the service locator based on the specified environment. The @injectableInit annotation triggers code generation, automatically registering your dependencies.

Registering Dependencies

You’ll register your services and classes in a injectable configuration file. Here’s an example:

@module
abstract class RegisterModule {

  @lazySingleton
  Connectivity get connectivity => Connectivity();

  @lazySingleton
  InternetConnectionChecker get internetConnectionChecker => InternetConnectionChecker();

  @lazySingleton
  NetworkInfoImpl get networkInfo => NetworkInfoImpl(
        connectivity: connectivity,
        internetConnectionChecker: internetConnectionChecker,
      );

  @lazySingleton
  NetworkInfoBloc get networkInfoBloc => NetworkInfoBloc(
        networkInfo: getIt<NetworkInfo>(),
        connectivity: getIt<Connectivity>(),
      );
}

This file registers the Connectivity, InternetConnectionChecker, NetworkInfoImpl, and NetworkInfoBloc classes as lazy singletons, meaning that instances will be created only when needed.

2. Implementing the Network Connectivity Checker

NetworkInfo Interface and Implementation

The NetworkInfo interface abstracts the network connectivity checking functionality. Here’s how it’s defined:

abstract class NetworkInfo {
  Future<bool> get isConnected;
}

The implementation of NetworkInfo relies on two packages: Connectivity to check the type of network connection, and InternetConnectionChecker to verify actual internet connectivity:

@LazySingleton(as: NetworkInfo)
class NetworkInfoImpl implements NetworkInfo {
  final Connectivity connectivity;
  final InternetConnectionChecker internetConnectionChecker;

  const NetworkInfoImpl({
    required this.connectivity,
    required this.internetConnectionChecker,
  });

  @override
  Future<bool> get isConnected async {
    try {
      bool isDeviceConnected = false;
      final connectivityResult = await connectivity.checkConnectivity();
      debugPrint('Connectivity Result: $connectivityResult');

      if (connectivityResult != ConnectivityResult.none) {
        isDeviceConnected = await internetConnectionChecker.hasConnection ||
            await hasInternetConnection();
      }
      debugPrint('Device Connected: $isDeviceConnected');
      return isDeviceConnected;
    } catch (e) {
      debugPrint('Error checking network connection: $e');
      return false;
    }
  }

  // not necessary
  Future<bool> hasInternetConnection() async {
    try {
      final response = await http.get(Uri.parse('https://www.google.com')).timeout(
        const Duration(seconds: 5),
      );
      if (response.statusCode == 200) {
        return true;
      }
    } catch (e) {
      debugPrint('Error checking internet connection: $e');
    }
    return false;
  }
}

This implementation first checks whether any network is available and then verifies the actual connectivity by checking with Google’s servers.

3. Creating the BLoC for Network Connectivity

Setting Up BLoC

The BLoC pattern separates business logic from the UI layer, making your code more maintainable and testable. Here’s the setup for NetworkInfoBloc:

@injectable
class NetworkInfoBloc extends Bloc<NetworkInfoEvent, NetworkInfoState> {
  final NetworkInfo networkInfo;
  final Connectivity connectivity;
  late StreamSubscription<ConnectivityResult> connectivitySubscription;

  NetworkInfoBloc({
    required this.networkInfo,
    required this.connectivity,
  }) : super(NetworkInfoState.initial()) {
    EventTransformer<T> debounce<T>(Duration duration) {
      return (events, mapper) => events.debounceTime(duration).flatMap(mapper);
    }

    // Use debounce for the CheckNetwork event
    on<CheckNetwork>(
      _onCheckNetwork,
      transformer: debounce(
        const Duration(seconds: 1),
      ),
    );

    connectivitySubscription = connectivity.onConnectivityChanged.listen((connectivityResult) async {
      await Future.delayed(const Duration(seconds: 1)); // Small delay
      debugPrint('Connectivity Result after delay: $connectivityResult');
      add(const CheckNetwork());
    });
  }

  Future<void> _onCheckNetwork(
    CheckNetwork event,
    Emitter<NetworkInfoState> emit,
  ) async {
    final isConnected = await networkInfo.isConnected;
    if (state.networkStatus != isConnected) {
      emit(state.copyWith(networkStatus: isConnected));
    }
    debugPrint(
        'Network Status ==> ${isConnected ? "Data connection is available." : "You are disconnected from the internet."}');
  }

  @override
  Future<void> close() {
    connectivitySubscription.cancel();
    return super.close();
  }
}
  • Event Handling: The BLoC listens for CheckNetwork events and checks the network status. If there’s a change, it emits a new state.

  • Debouncing Events: The EventTransformer debounces the events to avoid frequent unnecessary checks within a short time.

  • Listening to Connectivity Changes: The Connectivity package is used to listen for changes in connectivity and triggers a network check.

Events and States

Events and states are managed using the freezed package, which simplifies the creation of immutable classes.

  • Event Definition:

      part of 'network_info_bloc.dart';
    
      @freezed
      class NetworkInfoEvent with _$NetworkInfoEvent {
        const factory NetworkInfoEvent.checkNetwork() = CheckNetwork;
      }
    

    This event triggers a network status check.

  • State Definition:

      part of 'network_info_bloc.dart';
    
      @freezed
      class NetworkInfoState with _$NetworkInfoState {
        const factory NetworkInfoState({required bool networkStatus}) = _NetworkInfoState;
    
        factory NetworkInfoState.initial() => const NetworkInfoState(
          networkStatus: true,
        );
      }
    

    The state class holds the network status, which is either true (connected) or false (disconnected).

Run:

dart run build_runner build --delete-conflicting-outputs

4. Integrating the BLoC with the UI

Finally, let’s integrate the NetworkInfoBloc into your Flutter app. Here’s an example of how to use it in a StatefulWidget:

final networkInfoBloc = getIt<NetworkInfoBloc>();

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

  networkInfoBloc.stream.listen((state) {
    if (state.networkStatus) {
      toastInfo(
        msg: "Data connection is available.",
        status: Status.success,
      );
    } else {
      toastInfo(
        msg: "You are disconnected from the internet.",
        status: Status.error,
      );
    }
  });
}

This snippet shows how to listen to the BLoC’s state stream and display a toast notification based on the network status.

5. Displaying Toast Notifications

To provide user feedback, we can use the fluttertoast package. Here’s a helper function for showing toast messages:

import 'package:fluttertoast/fluttertoast.dart';

enum Status { success, error }

void toastInfo({
  required String msg,
  required Status status,
}) {
  Fluttertoast.showToast(
    msg: msg,
    toastLength: Toast.LENGTH_SHORT,
    gravity: ToastGravity.BOTTOM,
    backgroundColor: status == Status.success ? Colors.green : Colors.red,
    textColor: Colors.white,
    fontSize: 16.0,
  );
}

This function allows you to show either success or error messages, depending on the network status.

By implementing the BLoC pattern for network connectivity checks, combined with dependency injection and state management, you can create a responsive and well-architected Flutter application. The use of packages like getIt, injectable, freezed, Connectivity, and InternetConnectionChecker helps you manage dependencies, events, and network status efficiently.


This article and code should provide you with all the necessary tools and understanding to implement and manage network connectivity in your Flutter applications using the BLoC pattern effectively.

PS: Use a physical device to get the actual result, happy coding :)

Did you find this article valuable?

Support Flutter Aware by becoming a sponsor. Any amount is appreciated!