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) orfalse
(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 :)