flutter official logo

Building Network-Aware Flutter Apps: Handling Offline States Gracefully

The Importance of Network Awareness

Mobile users are constantly on the move, switching between Wi-Fi and cellular data or entering dead zones. A well-built Flutter app should never leave a user staring at a broken 'Load Failed' screen without context. Instead, it should react dynamically to connection changes. Building a 'network-aware' UI ensures that users understand why data isn't loading and provides a seamless transition when the signal returns.

Setting Up Connectivity Monitoring

To monitor network status in Flutter, the community standard is the connectivity_plus package. This library allows you to check the current status and, more importantly, listen to a stream of updates. First, add the dependency to your pubspec.yaml file.

dependencies:
  connectivity_plus: ^6.0.0

Creating a Global Connectivity Wrapper

Rather than checking the network status on every single page, a more robust architectural pattern is to create a global wrapper. This widget wraps your MaterialApp or specific screens and reacts to the network stream using a StreamBuilder.

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

class ConnectivityWrapper extends StatelessWidget {
  final Widget child;
  const ConnectivityWrapper({required this.child, super.key});

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<List<ConnectivityResult>>(
      stream: Connectivity().onConnectivityChanged,
      builder: (context, snapshot) {
        final connectivityResults = snapshot.data ?? [ConnectivityResult.none];
        
        if (connectivityResults.contains(ConnectivityResult.none)) {
          return Scaffold(
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Icon(Icons.wifi_off, size: 64, color: Colors.grey),
                  const SizedBox(height: 16),
                  const Text(
                    'No Internet Connection',
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                  ),
                  const Text('Please check your settings.'),
                ],
              ),
            ),
          );
        }
        return child;
      },
    );
  }
}

Refining the User Experience

While a full-screen block is useful for apps that require a constant connection (like a real-time trading app), most apps benefit from a more subtle approach. You can use a Stack to overlay a small 'Offline' banner at the bottom of the screen. This allows the user to still browse cached content while being aware that new data cannot be fetched.

Widget build(BuildContext context) {
  return Stack(
    children: [
      child,
      if (isOffline)
        Positioned(
          bottom: 0,
          left: 0,
          right: 0,
          child: Container(
            color: Colors.red,
            padding: const EdgeInsets.symmetric(vertical: 8),
            child: const Text(
              'You are currently offline',
              textAlign: TextAlign.center,
              style: TextStyle(color: Colors.white),
            ),
          ),
        ),
    ],
  );
}

Best Practices for Offline Resilience

Monitoring the connection is only half the battle. To truly master the offline experience, consider the following strategies:

  • Local Caching: Use sqflite or Hive to store data locally. When the network is unavailable, serve the cached version immediately.
  • Debouncing Status Changes: Network signals can flicker. Use a small delay before updating the UI to prevent the 'Offline' banner from flashing during rapid handoffs between Wi-Fi and 5G.
  • Retry Logic: When the connection is restored, trigger a refresh of your data providers automatically to ensure the UI is up to date without requiring a manual swipe-to-refresh.