← Back to posts Cover image for Mastering Real-Time Connectivity: Detecting True Internet Access in Flutter

Mastering Real-Time Connectivity: Detecting True Internet Access in Flutter

· 5 min read
Weekly Digest

The Flutter news you actually need

No spam, ever. Unsubscribe in one click.

Chris
By Chris

The Network Illusion: Why “Connected” ≠ “Online” in Flutter

If you’ve ever opened your Flutter app at an airport, hotel, or coffee shop and seen it stubbornly refuse to load data despite showing a strong WiFi connection, you’ve encountered the network illusion. Your device is connected to a local network, but that network has no real internet access. This happens with captive portals (those “click here to connect” WiFi pages), malfunctioning routers, or ISP outages.

The root problem? Most popular connectivity plugins only check if you’re connected to a network interface (WiFi, cellular), not whether that connection can actually reach the internet. This leads to a poor user experience: your app thinks it’s online, attempts API calls, and fails with cryptic timeouts or errors.

The Naïve Approach (And Why It Fails)

Many developers start with a simple ping or a single HTTP request:

Future<bool> hasInternet() async {
  try {
    final response = await http.get(Uri.parse('https://www.google.com'));
    return response.statusCode == 200;
  } catch (e) {
    return false;
  }
}

This is better than checking the network interface alone, but it has significant flaws:

  • Single Point of Failure: If Google is down or blocked in your region, your app incorrectly reports no internet.
  • No Persistent Monitoring: This is a one-time check. Network conditions change.
  • High Latency: You have to wait for the request to complete (or timeout) every time you check.

Building a Robust Real-Time Connectivity Checker

A reliable solution needs to be continuous, efficient, and intelligent. Let’s build a ConnectionManager class that embodies these principles.

First, add the necessary dependency to your pubspec.yaml:

dependencies:
  connectivity_plus: ^5.0.2
  http: ^1.1.0

Now, let’s create our connectivity service:

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

class ConnectionManager {
  final List<Uri> _testUris = [
    Uri.parse('https://www.gstatic.com/generate_204'), // Google's 204 endpoint
    Uri.parse('https://connectivitycheck.android.com/generate_204'),
    Uri.parse('https://clients3.google.com/generate_204'),
  ];
  
  final StreamController<bool> _connectionController = 
      StreamController<bool>.broadcast();
  Timer? _pollingTimer;
  bool _lastStatus = false;
  
  Stream<bool> get connectionStream => _connectionController.stream;
  bool get currentStatus => _lastStatus;
  
  Future<bool> _performRealCheck() async {
    // First, check if we have any network interface at all
    final connectivityResult = await Connectivity().checkConnectivity();
    if (connectivityResult == ConnectivityResult.none) {
      return false;
    }
    
    // Try multiple endpoints to avoid single-point failures
    for (final uri in _testUris) {
      try {
        final response = await http.get(uri).timeout(
          const Duration(seconds: 3),
        );
        // A 204 status or any successful 2xx status indicates real connectivity
        if (response.statusCode == 204 || 
            (response.statusCode >= 200 && response.statusCode < 300)) {
          return true;
        }
      } catch (_) {
        // Try the next endpoint
        continue;
      }
    }
    
    return false;
  }
  
  void startMonitoring({Duration interval = const Duration(seconds: 5)}) {
    // Stop any existing timer
    stopMonitoring();
    
    // Perform initial check
    _checkAndNotify();
    
    // Set up periodic checks
    _pollingTimer = Timer.periodic(interval, (_) => _checkAndNotify());
  }
  
  Future<void> _checkAndNotify() async {
    final hasConnection = await _performRealCheck();
    
    // Only notify listeners if the status changed
    if (hasConnection != _lastStatus) {
      _lastStatus = hasConnection;
      _connectionController.add(hasConnection);
    }
  }
  
  void stopMonitoring() {
    _pollingTimer?.cancel();
    _pollingTimer = null;
  }
  
  void dispose() {
    stopMonitoring();
    _connectionController.close();
  }
}

Key Design Decisions Explained

  1. Multiple Endpoints: We test against three reliable endpoints. If one fails (blocked, down, or unreachable), others can still confirm connectivity. The 204 status endpoints are ideal—they return no content, minimizing data usage.

  2. Two-Layer Check: We first use connectivity_plus to rule out the obvious case of no network interface. This is cheap and fast. Only if that passes do we proceed to the more expensive HTTP checks.

  3. Change-Only Notification: The manager only broadcasts updates when connectivity actually changes. This prevents unnecessary widget rebuilds.

  4. Configurable Polling: The 5-second default interval balances responsiveness with battery/data usage. Adjust based on your app’s needs.

Integrating with Your App

Here’s how to use this in a typical Flutter app:

class MyApp extends StatelessWidget {
  final ConnectionManager connectionManager = ConnectionManager();
  
  MyApp({super.key}) {
    connectionManager.startMonitoring();
  }
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: StreamBuilder<bool>(
        stream: connectionManager.connectionStream,
        initialData: false,
        builder: (context, snapshot) {
          final isOnline = snapshot.data ?? false;
          
          return Scaffold(
            appBar: AppBar(
              title: const Text('Network Aware App'),
              backgroundColor: isOnline ? Colors.green : Colors.orange,
            ),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(
                    isOnline ? Icons.cloud : Icons.cloud_off,
                    size: 80,
                    color: isOnline ? Colors.green : Colors.grey,
                  ),
                  const SizedBox(height: 20),
                  Text(
                    isOnline 
                        ? 'You have real internet access'
                        : 'No internet connection\n(Captive portal or offline network)',
                    textAlign: TextAlign.center,
                    style: const TextStyle(fontSize: 18),
                  ),
                  if (!isOnline) ...[
                    const SizedBox(height: 30),
                    ElevatedButton(
                      onPressed: () {
                        // Guide user to open browser for captive portal
                      },
                      child: const Text('Open WiFi Login Page'),
                    ),
                  ],
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

Common Pitfalls to Avoid

  1. Over-Polling: Checking too frequently (e.g., every second) drains battery and data. For most apps, 5-10 second intervals are sufficient.

  2. Relying on a Single Service: Don’t depend on just one endpoint. Services go down, get blocked, or become slow.

  3. Ignoring Network Changes: Remember to check when the app resumes from background, not just on a timer.

  4. Blocking the UI: Always perform checks asynchronously. Never await connectivity checks in your build methods.

  5. Forgetting to Clean Up: Always cancel timers and close streams when they’re no longer needed to prevent memory leaks.

When to Use This Pattern

This approach is particularly valuable for:

  • Apps that sync data in the background
  • Video/audio streaming applications
  • Real-time collaboration tools
  • Any app where offline functionality differs significantly from online mode

For simpler apps that only need occasional checks, consider using the ConnectionManager on-demand rather than continuously. Call _performRealCheck() only when you’re about to make an API request.

By implementing a robust connectivity checker, you transform a frustrating user experience into a transparent, informative one. Your users will appreciate knowing exactly why their data isn’t loading, and you’ll reduce support requests about “the app not working on WiFi.”

Remember: true connectivity checking isn’t about adding complexity—it’s about respecting the user’s actual network conditions and adapting gracefully to them.

This blog is produced with the assistance of AI by a human editor. Learn more

Related Posts

Cover image for Flutter's Hidden Gem: Building Powerful Linux Desktop Apps with Ease

Flutter's Hidden Gem: Building Powerful Linux Desktop Apps with Ease

Flutter is gaining traction as a robust framework for Linux desktop development, offering a smoother experience compared to traditional toolkits like GTK and Qt. This post will explore why Flutter excels for Linux apps, highlight its advantages, and provide practical insights for developers looking to leverage Flutter for their desktop projects.

Cover image for Simplifying Flutter Desktop Deployment: Signing and Distribution for Windows

Simplifying Flutter Desktop Deployment: Signing and Distribution for Windows

Deploying Flutter desktop apps on Windows can be challenging, especially regarding code signing and preventing Windows from blocking installations. This post will guide developers through the process of signing their Flutter desktop applications, exploring alternatives to the Microsoft Store like creating executable installers, and covering the necessary steps to ensure a smooth user experience.

Cover image for Mastering Flutter Tooling: Streamlining SDK Management and Installation on Windows

Mastering Flutter Tooling: Streamlining SDK Management and Installation on Windows

Developers frequently voice frustrations about Flutter SDK version management and installation processes, particularly on Windows. This post will explore best practices for managing multiple Flutter versions using tools like FVM, discuss integrating Flutter with package managers like Winget for a smoother Windows setup, and offer strategies to ensure a consistent development environment across projects and teams.