Conectividade
- Conectividade
Introduction
In today’s world, where mobile apps rely heavily on internet connectivity, ensuring that your Flutter app gracefully handles connection changes is crucial. In this article, we’ll explore the common issues developers face when not handling connection changes correctly and provide a comprehensive guide on how to manage connectivity changes in your Flutter app. We’ll also share a well-structured code example for you to follow along and implement in your projects.
The Problem
Without proper connection handling, your app can suffer from various issues. Users might experience functionality disruptions, unexpected errors, or a poor user experience. Imagine a scenario where a user loses their internet connection while using your app; if the app doesn’t handle this gracefully, it might crash or display confusing error messages.
The solution
- ConnectivityService Class: A service class for managing connectivity status.
- ConnectionAwareMixin: A mixin that provides connection-aware functionality to
StateaStatefulWidget. - DefaultConnectionAwareStateMixin: Provides default UI and behavior for connection changes.
How It Works:
- The
ConnectivityServiceclass constantly monitors connectivity status and handles VPN connections. - The
ConnectionAwareMixinmixin ensures that stateful widgets respond to connection changes by tracking and updating the UI. - The
DefaultConnectionAwareStateMixinprovides a default behavior, and reloading state, and offers a "Retry" option when there's no connection.
ConnectivityService:
/// A service class for managing connectivity status.
class ConnectivityService {
ConnectivityService(this._connectivity)
: _onConnectivityChanged = _connectivity.onConnectivityChanged
.map(_onResult)
.asBroadcastStream();
// ignore: unused_field
final Connectivity _connectivity;
final Stream<bool> _onConnectivityChanged;
StreamSubscription<bool>? _subscription;
/// A stream of boolean values indicating the current connectivity status.
///
/// This stream emits `true` when the device has an active connection and
/// `false` when there's no active connection.
Stream<bool> get onConnectivityChanged => _onConnectivityChanged;
bool _hasConnection = false;
/// Checks if there is an active connection.
bool get hasActiveConnection => _hasConnection;
/// Initializes the [ConnectivityService].
///
/// This method should be called to initialize the service. It checks the
/// initial connectivity status and sets up a listener for connectivity changes.
Future<ConnectivityService> init() async {
final result = await _connectivity.checkConnectivity();
_subscription = onConnectivityChanged.listen(_onChange);
_hasConnection = _onResult(result);
return this;
}
void _onChange(bool value) {
_hasConnection = value;
}
static bool _onResult(ConnectivityResult result) {
bool hasConnection = false;
/// TODO: Add logic to handle for case [vpn] in android and iOS.
if (result case ConnectivityResult.mobile || ConnectivityResult.wifi) {
hasConnection = true;
}
return hasConnection;
}
void dispose() {
_subscription?.cancel();
_subscription = null;
}
}
RestartableStateInterface:
/// An abstract interface class for managing restartable states.
///
/// Classes implementing this interface are responsible for managing the state
/// that can be loaded, reloaded, and checked for the need of reloading.
abstract interface class RestartableStateInterface {
/// Loads the state asynchronously.
///
/// Subclasses should implement this method to perform the necessary
/// operations for loading the state.
Future<void> loadState();
/// Reloads the state by calling [loadState] again.
///
/// This method triggers the reloading of the state by invoking the [loadState]
/// method. Subclasses should implement the [loadState] method to handle the
/// reloading process.
Future<void> reloadState();
/// Checks if the state needs to be reloaded.
///
/// Subclasses should implement this method to provide a logic that determines
/// whether the state should be reloaded.
///
/// Returns `true` if the state should be reloaded, otherwise `false`.
bool shouldReload();
}
ConnectionAwareMixin:
/// A mixin that provides connection-aware functionality to a [State] of a
/// [StatefulWidget].
///
/// This mixin extends the capabilities of a stateful widget by adding
/// connection-aware behavior. It manages connectivity changes and allows
/// reloading the state when the connection is restored,
/// based on the [shouldReload] logic.
///
/// The mixin requires the stateful widget it is mixed into to implement the
/// [RestartableStateInterface] interface.
mixin ConnectionAwareMixin<T extends StatefulWidget> on State<T> {
/// Tracks the current connection status.
bool hasConnection = false;
bool get initialConnectionState;
Stream<bool> get onConnectivityChanged;
/// Subscription for listening to connection changes.
StreamSubscription<bool>? _subscription;
/// Notifies the consumers when there is a change in connectivity state.
///
/// The implementing state should override this method to respond to changes
/// in connection status.
void onConnectionStateChange();
void initState() {
super.initState();
hasConnection = initialConnectionState;
_subscription = onConnectivityChanged.listen(_onChangeConnection);
}
/// Listens for changes in connectivity from [ConnectivityService] and notifies
/// the current state.
///
/// If [ConnectivityService.onConnectivityChanged] emits the same state as
/// [ConnectivityService.hasActiveConnection], no state update is triggered.
/// Otherwise, the state is updated and notifies the client with via
/// [onConnectionStateChange].
void _onChangeConnection(bool result) {
hasConnection = result;
onConnectionStateChange();
}
void dispose() {
_subscription?.cancel();
_subscription = null;
super.dispose();
}
}
DefaultConnectionAwareStateMixin:
/// A mixin that provides the provides default UI and behavior for connection
/// changes.
///
/// This mixin is designed to be used in conjunction with the [ConnectionAwareMixin]
/// and enhances its functionality by providing a default implementation for the
/// [build] method. It handles rendering the user interface based on the connection
/// status. If there is an active connection, it uses the [buildPage] method to render
/// the content; otherwise, it displays a default no-connection state.
mixin DefaultConnectionAwareStateMixin<T extends StatefulWidget>
on ConnectionAwareMixin<T> implements RestartableStateInterface {
Stream<bool> get onConnectivityChanged =>
connectionService.onConnectivityChanged;
bool get initialConnectionState => connectionService.hasActiveConnection;
void initState() {
super.initState();
reloadState();
}
/// Reloads the state of a widget in response to connection status changes.
///
/// This method triggers a reload of the widget's state when called.
/// It is often used to refresh the UI in response to changes in the
/// connection status or any other relevant data.
Future<void> reloadState() async {
await loadState();
}
/// Loads the state of a widget in response to connection status changes.
///
/// This method is responsible for updating the widget's state. It is
/// often overridden to fetch data from a data source or perform any other
/// necessary operations when the connection status changes.
///
/// Example:
/// ```dart
/// @override
/// FutureOr<void> loadState() async {
/// final newData = await fetchData(); // Fetch data from a data source
/// setState(() {
/// data = newData; // Update the state with the new data
/// });
/// }
/// ```
Future<void> loadState() async {
setState(() {});
}
/// Determines whether the widget should be reloaded based on connection changes.
///
/// This method provides a way to control whether the widget's state should be
/// reloaded when the connection is restored. Returning `true` indicates that
/// the widget's state should be reloaded; returning `false` means that no
/// reloading will occur.
///
/// Example:
/// ```dart
/// @override
/// bool shouldReload() {
/// return data.isEmpty; // Reload only if data is empty
/// }
/// ```
bool shouldReload() => true;
/// Default implementation for handling changes in connection status within
/// a [ConnectionAwareMixin] mixin.
///
/// This method is automatically called when the connection status changes.
/// It provides a default behavior for responding to changes in connection state.
/// If [hasConnection] is `true` and the [shouldReload] method returns `true`,
/// the [reloadState] method is called to reload the state. If either condition
/// is not met, a call to [setState] is made to update the UI based on the
/// current connection status.
void onConnectionStateChange() {
if (hasConnection && shouldReload()) {
reloadState();
} else {
setState(() {});
}
}
/// Describes the part of the user interface represented by this widget where
/// there is an active connection.
///
/// The implementing state should provide the content to be displayed when there
/// is an active connection.
Widget buildPage(BuildContext context);
/// Default implementation of [State.build] when the state is mixed with
/// [ConnectionAwareMixin].
///
/// If there is an active connection available, it will render the user interface
/// provided by [buildPage]. Otherwise, it will provide a default [NoConnectionState]
/// as a fallback.
///
/// Additionally wraps the [NoConnectionState] with [WillPopScope] to handle
/// system navigation pop. If there is absence of connection, in that case
/// pops current flutter activity.
Widget build(BuildContext context) {
if (hasConnection) {
return buildPage(context);
} else {
return WillPopScope(
onWillPop: () async {
SystemNavigator.pop(animated: true);
return false;
},
child: NoConnectionState(
onRetry: reloadState,
),
);
}
}
}
NoConnectionState Widget:
class NoConnectionState extends StatelessWidget {
const NoConnectionState({super.key, required this.onRetry});
final VoidCallback onRetry;
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('No active connection'),
const SizedBox(
height: 20,
),
OutlinedButton(onPressed: onRetry, child: const Text('Retry'))
],
),
),
);
}
}
Putting It All Together: Example App
Let’s bring it all together by walking you through a complete example app that demonstrates how to utilize the provided code. In this example, we’ll use the ConnectivityService, mixins, and the NoConnectionState widget to handle connection changes gracefully.
Here is the visual output of the above:

Conclusion
In conclusion, handling connection changes in your Flutter app is vital for providing a seamless user experience. By implementing the solutions presented in this article, you can ensure that your app gracefully responds to changes in connectivity, avoiding crashes and delivering a smoother user experience.
Contribute and Collaborate
We believe in the power of collaboration and community contributions. If you’d like to explore the code in more detail, suggest improvements, or contribute to this project, we welcome your involvement. Your contributions can help make this solution even more robust and beneficial to the Flutter community.
- GitHub Repository: https://github.com/DK070202/connectivity
- You can access the full source code, open issues, and submit pull requests on our GitHub repository.
Feel free to explore the code, share your feedback, report issues, or contribute enhancements. Together, we can create better Flutter apps with seamless connection handling.