connectivity_validator 0.0.4
connectivity_validator: ^0.0.4 copied to clipboard
A robust internet connectivity checker that validates actual network access using native Android (NET_CAPABILITY_VALIDATED) and iOS (NWPathMonitor) APIs.
connectivity_validator #
A Flutter plugin that provides validated internet connectivity status with real-time updates. Unlike basic connectivity checks, this plugin validates that the device has actual internet access (not just a network connection), detecting captive portals and ensuring the connection is truly functional.
Features #
- ✅ Validated Connectivity: Checks for real internet access, not just network connection
- ✅ Captive Portal Detection: Detects when connected to WiFi but behind a captive portal
- ✅ Router Internet Loss Detection: Detects when WiFi router loses internet while staying connected
- ✅ HTTPS Connectivity Testing: Performs actual HTTPS requests to verify real internet access
- ✅ Smart Failure Handling: Prevents ping-pong effects with intelligent failure counter
- ✅ Real-time Updates: Stream-based API for continuous connectivity monitoring
- ✅ Cross-platform: Works on both Android and iOS with identical behavior
- ✅ Lightweight: Zero dependencies, framework-agnostic
- ✅ Optimized: Only emits updates when connectivity state actually changes
- ✅ Battery Efficient: Smart caching and periodic checks balance accuracy with performance
Installation #
Add this to your package's pubspec.yaml file:
dependencies:
connectivity_validator: ^0.0.4
Then run:
flutter pub get
Platform Setup #
Android #
The plugin requires the following permissions in your AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Required permission to check network connectivity state -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Optional: For WiFi-specific connectivity checks -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
...
</manifest>
Note: These permissions are typically already included in Flutter apps, but if you encounter permission errors, ensure they're declared in your app's AndroidManifest.xml file.
The plugin uses ConnectivityManager with NET_CAPABILITY_VALIDATED and HTTPS connectivity testing to ensure real internet connectivity.
Note: The plugin also requires INTERNET permission for HTTPS connectivity tests. This is typically already included in Flutter apps.
iOS #
No additional setup required. The plugin uses NWPathMonitor with .satisfied status and HTTPS connectivity testing to detect validated connectivity.
Note: The plugin supports both CocoaPods and Swift Package Manager (SPM). Flutter will automatically use the appropriate dependency manager based on your project configuration.
Basic Usage #
The plugin provides a simple stream-based API:
import 'package:connectivity_validator/connectivity_validator.dart';
final validator = ConnectivityValidator();
// Listen to connectivity changes
validator.onConnectivityChanged.listen((isOnline) {
if (isOnline) {
print('Internet is available and validated');
} else {
print('No internet or captive portal detected');
}
});
State Management Options #
The plugin is framework-agnostic and works with any state management solution. Here are examples for popular approaches:
Option 1: Using StreamBuilder (No Dependencies) #
The simplest approach using Flutter's built-in StreamBuilder:
import 'package:connectivity_validator/connectivity_validator.dart';
import 'package:flutter/material.dart';
class ConnectivityWidget extends StatelessWidget {
final ConnectivityValidator validator = ConnectivityValidator();
@override
Widget build(BuildContext context) {
return StreamBuilder<bool>(
stream: validator.onConnectivityChanged,
initialData: false,
builder: (context, snapshot) {
final isOnline = snapshot.data ?? false;
return Text(
isOnline ? 'Online' : 'Offline',
style: TextStyle(
color: isOnline ? Colors.green : Colors.red,
),
);
},
);
}
}
Option 2: Using GetX #
Create a controller that extends GetxController:
import 'dart:async';
import 'package:connectivity_validator/connectivity_validator.dart';
import 'package:get/get.dart';
class NetworkStatusController extends GetxController {
var isOffline = false.obs;
StreamSubscription? _subscription;
@override
void onInit() {
super.onInit();
_startListening();
}
@override
void onClose() {
_subscription?.cancel();
super.onClose();
}
void _startListening() {
_subscription = ConnectivityValidator()
.onConnectivityChanged
.listen((isOnline) {
isOffline.value = !isOnline;
});
}
}
Usage in your app:
import 'package:get/get.dart';
void main() {
Get.put(NetworkStatusController(), permanent: true);
runApp(MyApp());
}
// In your widget
Obx(() => Text(
Get.find<NetworkStatusController>().isOffline.value
? 'Offline'
: 'Online'
))
Option 3: Using Provider #
Create a provider class:
import 'dart:async';
import 'package:connectivity_validator/connectivity_validator.dart';
import 'package:flutter/foundation.dart';
class ConnectivityProvider extends ChangeNotifier {
bool _isOnline = false;
StreamSubscription? _subscription;
final ConnectivityValidator _validator = ConnectivityValidator();
bool get isOnline => _isOnline;
ConnectivityProvider() {
_startListening();
}
void _startListening() {
_subscription = _validator.onConnectivityChanged.listen((isOnline) {
_isOnline = isOnline;
notifyListeners();
});
}
@override
void dispose() {
_subscription?.cancel();
super.dispose();
}
}
Usage:
// Wrap your app
ChangeNotifierProvider(
create: (_) => ConnectivityProvider(),
child: MyApp(),
)
// In your widget
Consumer<ConnectivityProvider>(
builder: (context, provider, child) {
return Text(provider.isOnline ? 'Online' : 'Offline');
},
)
Option 4: Using Riverpod #
Create a provider:
import 'dart:async';
import 'package:connectivity_validator/connectivity_validator.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final connectivityProvider = StreamProvider<bool>((ref) {
return ConnectivityValidator().onConnectivityChanged;
});
// Or with state management
class ConnectivityNotifier extends StateNotifier<bool> {
StreamSubscription? _subscription;
ConnectivityNotifier() : super(false) {
_subscription = ConnectivityValidator()
.onConnectivityChanged
.listen((isOnline) {
state = isOnline;
});
}
@override
void dispose() {
_subscription?.cancel();
super.dispose();
}
}
final connectivityNotifierProvider =
StateNotifierProvider<ConnectivityNotifier, bool>((ref) {
return ConnectivityNotifier();
});
Usage:
// With StreamProvider
final connectivity = ref.watch(connectivityProvider);
connectivity.when(
data: (isOnline) => Text(isOnline ? 'Online' : 'Offline'),
loading: () => CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
)
// With StateNotifier
final isOnline = ref.watch(connectivityNotifierProvider);
Text(isOnline ? 'Online' : 'Offline')
Option 5: Using BLoC #
Create a BLoC:
import 'dart:async';
import 'package:connectivity_validator/connectivity_validator.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class ConnectivityBloc extends Bloc<ConnectivityEvent, ConnectivityState> {
StreamSubscription? _subscription;
ConnectivityBloc() : super(ConnectivityInitial()) {
_subscription = ConnectivityValidator()
.onConnectivityChanged
.listen((isOnline) {
add(ConnectivityChanged(isOnline));
});
}
@override
Stream<ConnectivityState> mapEventToState(ConnectivityEvent event) async* {
if (event is ConnectivityChanged) {
yield ConnectivityLoaded(event.isOnline);
}
}
@override
Future<void> close() {
_subscription?.cancel();
return super.close();
}
}
// Events
abstract class ConnectivityEvent {}
class ConnectivityChanged extends ConnectivityEvent {
final bool isOnline;
ConnectivityChanged(this.isOnline);
}
// States
abstract class ConnectivityState {}
class ConnectivityInitial extends ConnectivityState {}
class ConnectivityLoaded extends ConnectivityState {
final bool isOnline;
ConnectivityLoaded(this.isOnline);
}
Usage:
BlocProvider(
create: (_) => ConnectivityBloc(),
child: BlocBuilder<ConnectivityBloc, ConnectivityState>(
builder: (context, state) {
if (state is ConnectivityLoaded) {
return Text(state.isOnline ? 'Online' : 'Offline');
}
return CircularProgressIndicator();
},
),
)
Option 6: Using ValueNotifier #
Simple reactive approach without external dependencies:
import 'dart:async';
import 'package:connectivity_validator/connectivity_validator.dart';
import 'package:flutter/material.dart';
class ConnectivityNotifier extends ValueNotifier<bool> {
StreamSubscription? _subscription;
ConnectivityNotifier() : super(false) {
_subscription = ConnectivityValidator()
.onConnectivityChanged
.listen((isOnline) {
value = isOnline;
});
}
@override
void dispose() {
_subscription?.cancel();
super.dispose();
}
}
Usage:
final notifier = ConnectivityNotifier();
ValueListenableBuilder<bool>(
valueListenable: notifier,
builder: (context, isOnline, child) {
return Text(isOnline ? 'Online' : 'Offline');
},
)
API Reference #
ConnectivityValidator #
The main class for accessing connectivity status.
Properties
onConnectivityChanged → Stream<bool>
Returns a stream of boolean values indicating connectivity status:
true: Internet is available and validated (active connection with real internet access)false: No internet connection or captive portal detected
The stream emits:
- Immediately when you start listening (initial state)
- Whenever the connectivity status changes
- Only when the state actually changes (optimized to avoid duplicate emissions)
Example
final validator = ConnectivityValidator();
// Get initial state
final initialStatus = await validator.onConnectivityChanged.first;
print('Initial status: ${initialStatus ? "Online" : "Offline"}');
// Listen to changes
validator.onConnectivityChanged.listen((isOnline) {
print('Connectivity changed: ${isOnline ? "Online" : "Offline"}');
});
How It Works #
Android #
The plugin uses Android's ConnectivityManager with NetworkCallback and HTTPS connectivity testing:
Two-Phase Validation System
-
Immediate Capability Checks (Fast Response)
- Monitors multiple callbacks:
onAvailable,onLost,onCapabilitiesChanged, andonLinkPropertiesChanged - Checks
NET_CAPABILITY_INTERNETandNET_CAPABILITY_VALIDATEDfor immediate updates - Sends updates instantly when capabilities change
- Monitors multiple callbacks:
-
HTTPS Connectivity Verification (Accurate Detection)
- When capabilities indicate online, performs HTTPS requests to Google's connectivity check endpoints
- Tests multiple endpoints:
www.google.com/generate_204,connectivitycheck.gstatic.com/generate_204,clients3.google.com/generate_204 - Uses 500ms timeout per request for fast verification
- Runs in background thread to avoid blocking UI
Smart Failure Handling
- Failure Counter: Tracks consecutive HTTPS test failures
- Override Logic: Only switches to OFFLINE after 2 consecutive HTTPS failures (prevents ping-pong effects)
- Respects HTTPS Results: When HTTPS confirms OFFLINE, periodic capability checks won't override it
- Automatic Reset: Counter resets when HTTPS test succeeds or capabilities say offline
Periodic Checks
- Runs every 2 seconds to detect router internet loss
- Verifies connectivity with HTTPS tests every 5 seconds
- Caches HTTPS test results for 5 seconds to balance accuracy with performance
This dual approach ensures:
- Fast response when network capabilities change
- Accurate detection when router loses internet (even if WiFi stays connected)
- No ping-pong effects from intermittent HTTPS test failures
- Battery efficient with smart caching and periodic checks
iOS #
The plugin uses iOS's NWPathMonitor with HTTPS connectivity testing:
Two-Phase Validation System
-
Immediate Path Status Checks (Fast Response)
- Monitors all network interfaces (WiFi, cellular, etc.)
- Checks
.satisfiedstatus for immediate updates - Sends updates instantly when path status changes
-
HTTPS Connectivity Verification (Accurate Detection)
- When path status indicates online, performs HTTPS requests to Google's connectivity check endpoints
- Tests multiple endpoints sequentially until one succeeds
- Uses 500ms timeout per request for fast verification
- Runs asynchronously to avoid blocking
Smart Failure Handling
- Failure Counter: Tracks consecutive HTTPS test failures
- Override Logic: Only switches to OFFLINE after 2 consecutive HTTPS failures (prevents ping-pong effects)
- Respects HTTPS Results: When HTTPS confirms OFFLINE, periodic path checks won't override it
- Automatic Reset: Counter resets when HTTPS test succeeds or path status says offline
Periodic Checks
- Runs every 2 seconds to detect router internet loss
- Verifies connectivity with HTTPS tests every 5 seconds
- Caches HTTPS test results for 5 seconds to balance accuracy with performance
This dual approach ensures:
- Fast response when network path status changes
- Accurate detection when router loses internet (even if WiFi stays connected)
- No ping-pong effects from intermittent HTTPS test failures
- Battery efficient with smart caching and periodic checks
Why HTTPS Testing? #
Both Android's NET_CAPABILITY_VALIDATED and iOS's NWPathMonitor.satisfied can be stale when:
- Router loses internet but WiFi connection remains active
- Network is behind a captive portal that hasn't been detected yet
- DNS issues prevent actual internet access
HTTPS connectivity testing provides a ground truth check by making actual requests to reliable endpoints, ensuring the device truly has internet access.
Example App #
The example app demonstrates usage with GetX. To run it:
cd example
flutter pub get
flutter run
The example shows:
- Real-time connectivity status updates
- Visual indicators (icons and colors)
- Proper state management with GetX
Best Practices #
-
Always dispose subscriptions: When using the stream, make sure to cancel the subscription in your
dispose()method to prevent memory leaks. -
Handle initial state: The stream emits immediately when you start listening, so handle the initial state appropriately.
-
Use appropriate state management: Choose a state management solution that fits your app's architecture. The plugin works with all of them.
-
Show user feedback: Display connectivity status clearly to users, especially when offline, so they understand why certain features aren't working.
-
Graceful degradation: Design your app to handle offline states gracefully without crashing.
Troubleshooting #
Stream not emitting updates #
- Ensure you're properly listening to the stream
- Check that you're not canceling the subscription prematurely
- On Android, verify that
ACCESS_NETWORK_STATEpermission is declared in yourAndroidManifest.xml
Permission errors on Android #
If you see an error like android.permission.ACCESS_NETWORK_STATE, add the required permissions to your app's AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
The plugin's manifest includes these permissions, but your app's manifest must also declare them.
Always showing offline #
- Check device network settings
- Verify you're not in airplane mode
- On Android, ensure the app has network access permissions declared in
AndroidManifest.xml - Check if HTTPS connectivity tests are being blocked by firewall or network restrictions
Ping-pong effect (rapidly switching between online/offline) #
The plugin includes smart failure handling to prevent this:
- Requires 2 consecutive HTTPS test failures before overriding native status
- Respects HTTPS test results when they conflict with native status
- If you still experience ping-pong, it may indicate network instability or firewall blocking HTTPS tests
iOS build issues #
- Ensure your iOS deployment target is 12.0 or higher (required for
NWPathMonitor) - Run
pod installin theiosdirectory if needed
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
License #
See the LICENSE file for details.
Support #
For issues, questions, or contributions, please visit the GitHub repository.