Maps, Location & Device Features

Monitoring Network Connectivity

16 min Lesson 11 of 12

Monitoring Network Connectivity

Modern mobile apps must handle unreliable network conditions gracefully. Rather than letting network calls fail silently, Flutter apps can use the connectivity_plus package to check the current network status at startup and listen for real-time changes — showing appropriate in-app feedback when the device goes offline or comes back online.

Adding the connectivity_plus Package

Add the package to your pubspec.yaml:

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  connectivity_plus: ^6.0.3

Run flutter pub get to fetch the dependency. On Android, no extra permissions are required for reading connectivity status. On iOS, the package works out of the box with no Info.plist changes needed.

Key API Surface

The package exposes a single Connectivity class with two main entry points:

  • checkConnectivity() — returns a Future<List<ConnectivityResult>> with the current connection type(s).
  • onConnectivityChanged — a Stream<List<ConnectivityResult>> that emits every time the network state changes.

The ConnectivityResult enum includes values such as wifi, mobile, ethernet, vpn, bluetooth, and none. A device may report multiple results simultaneously (e.g., both wifi and vpn).

Note: connectivity_plus reports the type of network interface, not whether the internet is actually reachable. A device connected to a WiFi network that has no upstream internet access will still return ConnectivityResult.wifi. For a true reachability check, combine this package with an HTTP probe or use internet_connection_checker_plus.

Checking Connectivity at Startup

The cleanest pattern is to perform an initial check inside initState and then immediately subscribe to the stream:

NetworkStatusService — check and listen

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

class NetworkAwarePage extends StatefulWidget {
  const NetworkAwarePage({super.key});

  @override
  State<NetworkAwarePage> createState() => _NetworkAwarePageState();
}

class _NetworkAwarePageState extends State<NetworkAwarePage> {
  final Connectivity _connectivity = Connectivity();
  late StreamSubscription<List<ConnectivityResult>> _subscription;

  bool _isOnline = true;
  ConnectivityResult _connectionType = ConnectivityResult.none;

  @override
  void initState() {
    super.initState();
    _checkInitialConnectivity();
    _subscription = _connectivity.onConnectivityChanged.listen(_onConnectivityChanged);
  }

  Future<void> _checkInitialConnectivity() async {
    final results = await _connectivity.checkConnectivity();
    _onConnectivityChanged(results);
  }

  void _onConnectivityChanged(List<ConnectivityResult> results) {
    final hasConnection = results.any(
      (r) => r != ConnectivityResult.none,
    );
    setState(() {
      _isOnline = hasConnection;
      _connectionType = results.isNotEmpty ? results.first : ConnectivityResult.none;
    });
  }

  @override
  void dispose() {
    _subscription.cancel();  // Always cancel to prevent memory leaks
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Network Demo')),
      body: Column(
        children: [
          if (!_isOnline)
            Container(
              width: double.infinity,
              color: Colors.red.shade700,
              padding: const EdgeInsets.symmetric(vertical: 8),
              child: const Text(
                'No internet connection',
                textAlign: TextAlign.center,
                style: TextStyle(color: Colors.white),
              ),
            ),
          Expanded(
            child: Center(
              child: Text(
                _isOnline
                    ? 'Online via $_connectionType'
                    : 'Offline',
                style: const TextStyle(fontSize: 18),
              ),
            ),
          ),
        ],
      ),
    );
  }
}
Tip: Always cancel the StreamSubscription in dispose(). Failing to do so keeps the subscription alive even after the widget is removed from the tree, causing memory leaks and potential setState-on-disposed-widget errors.

Extracting a Reusable ConnectivityService

For larger apps, isolate the connectivity logic in a dedicated service class and inject it into widgets via a state management solution such as Provider or Riverpod. This makes the service testable and avoids duplicating the Connectivity instance across the app.

ConnectivityService — reusable singleton

import 'dart:async';
import 'package:connectivity_plus/connectivity_plus.dart';

class ConnectivityService {
  final Connectivity _connectivity = Connectivity();

  // Expose a broadcast stream widgets can listen to
  Stream<bool> get onStatusChange => _connectivity.onConnectivityChanged.map(
        (results) => results.any((r) => r != ConnectivityResult.none),
      );

  /// Returns true if any non-none interface is available right now.
  Future<bool> get isConnected async {
    final results = await _connectivity.checkConnectivity();
    return results.any((r) => r != ConnectivityResult.none);
  }

  /// Human-readable label for the primary connection type.
  Future<String> get connectionLabel async {
    final results = await _connectivity.checkConnectivity();
    if (results.contains(ConnectivityResult.wifi)) return 'WiFi';
    if (results.contains(ConnectivityResult.mobile)) return 'Mobile Data';
    if (results.contains(ConnectivityResult.ethernet)) return 'Ethernet';
    return 'Offline';
  }
}

Showing In-App Feedback

Common patterns for surfacing connectivity changes to the user include:

  • Banner bar — a persistent colored strip at the top or bottom of the screen (shown above).
  • SnackBar — a transient notification that auto-dismisses, suitable for reconnection events.
  • Overlay / full-screen block — for apps where offline use is not supported at all.
  • Disabling actions — graying out upload/send buttons when offline.
Warning: Avoid showing a SnackBar on every connectivity change if the stream fires rapidly (e.g., when switching between WiFi and mobile). Debounce or throttle the stream, or only notify the user when the final state differs from the previous one, to prevent a cascade of toasts.

Summary

The connectivity_plus package provides two complementary tools: a one-shot checkConnectivity() future for startup probes and an onConnectivityChanged stream for reactive updates. The canonical pattern is to (1) add the package, (2) call the future in initState, (3) subscribe to the stream and update state, and (4) cancel the subscription in dispose(). For production apps, pair this with an actual internet reachability check and extract the logic into a shared service.