How FlClash's Connectivity Manager Detects and Responds to Network Changes

FlClash uses a stateful widget called ConnectivityManager that leverages the connectivity_plus plugin to monitor platform network events, automatically fetches the current Wi-Fi SSID via WifiSsidManager, and propagates changes through Riverpod providers to update the UI in real time.

The connectivity manager in FlClash centralizes network detection logic within a single widget that can be placed at the root of the application tree. This architecture ensures that any descendant widget can react to connectivity changes without implementing individual listeners, while the manager handles platform-specific details like SSID retrieval and provider updates.

Architecture of the Connectivity Manager

The manager is implemented as a StatefulWidget in lib/manager/connectivity_manager.dart that accepts a child widget and an optional callback. Rather than rendering UI itself, it acts as an invisible wrapper that maintains a subscription to the operating system's network state broadcasts and updates global application state accordingly.

This pattern decouples network detection from business logic, allowing the rest of the FlClash codebase to simply watch currentSSIDProvider for Wi-Fi name changes or use the callback for custom connectivity handling.

Step-by-Step Network Detection Flow

Initializing the Platform Listener

When the widget mounts, initState creates a subscription to Connectivity().onConnectivityChanged, a broadcast stream provided by the connectivity_plus package. This stream emits a List<ConnectivityResult> every time the OS reports a network transition—such as switching from Wi-Fi to mobile data or losing connection entirely.

// lib/manager/connectivity_manager.dart
@override
void initState() {
  super.initState();
  subscription = Connectivity().onConnectivityChanged.listen((results) {
    // Handle results...
  });
}

Wi-Fi Detection and SSID Retrieval

When the emitted list contains ConnectivityResult.wifi, the manager queries WifiSsidManager.instance.getSsid() to fetch the current network name. This asynchronous operation updates the currentSSIDProvider through globalState.container, making the SSID available to any watching widget.

// From lib/manager/connectivity_manager.dart
if (results.contains(ConnectivityResult.wifi)) {
  WifiSsidManager.instance.getSsid().then((ssid) {
    globalState.container.read(currentSSIDProvider.notifier).value = ssid;
  });
}

Handling Non-Wi-Fi States

If the connectivity results do not include Wi-Fi—indicating mobile data, Ethernet, or no connection—the manager explicitly clears the provider by setting it to null. This ensures that dependent UI elements correctly display offline status or switch to cellular-specific proxy rules.

// lib/manager/connectivity_manager.dart
else {
  globalState.container.read(currentSSIDProvider.notifier).value = null;
}

Support for External Callbacks

After internal state updates, the manager forwards the raw results list to an optional onConnectivityChanged callback if provided. This allows consumers to implement additional logic—such as logging, analytics, or offline banners—without modifying the manager's source code.

// lib/manager/connectivity_manager.dart
if (widget.onConnectivityChanged != null) {
  widget.onConnectivityChanged!(results);
}

Resource Cleanup

To prevent memory leaks, the subscription is cancelled in the dispose method when the widget is removed from the tree. This is critical for long-running applications where the connectivity listener might otherwise persist indefinitely.

// lib/manager/connectivity_manager.dart
@override
void dispose() {
  subscription.cancel();
  super.dispose();
}

State Management and Provider Structure

The SSID value is stored in currentSSIDProvider, a Riverpod provider generated in lib/providers/generated/app.g.dart. By writing to globalState.container.read(currentSSIDProvider.notifier), the manager ensures that any widget watching this provider automatically rebuilds when the network changes.

This provider-based approach eliminates the need for setState or callback drilling, allowing deeply nested widgets to react to connectivity changes with minimal boilerplate.

Implementation Examples

Wrapping the Application Root

Place ConnectivityManager at the top of the widget tree to ensure global network monitoring. The optional callback can be used for application-wide logging or analytics.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ConnectivityManager(
      onConnectivityChanged: (results) {
        debugPrint('Network changed: $results');
      },
      child: MaterialApp(
        home: HomeScreen(),
      ),
    );
  }
}

Consuming the Wi-Fi SSID

Widgets can read the current network name using Riverpod's ConsumerWidget. The UI updates automatically when the manager detects a new SSID or connection loss.

class WifiInfoTile extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final ssid = ref.watch(currentSSIDProvider);
    return ListTile(
      leading: const Icon(Icons.wifi),
      title: Text(ssid ?? 'Not connected to Wi-Fi'),
    );
  }
}

Reacting to Raw Connectivity Changes

For logic that depends on connection type rather than SSID, use the callback to implement custom behavior when the device goes offline or switches networks.

ConnectivityManager(
  onConnectivityChanged: (results) {
    if (results.contains(ConnectivityResult.none)) {
      // Trigger offline mode, pause sync, or show banner
    }
  },
  child: Scaffold(
    body: Dashboard(),
  ),
);

Key Source Files

  • lib/manager/connectivity_manager.dart – Implements the StatefulWidget that manages the connectivity_plus subscription, SSID updates, and optional callbacks.
  • lib/providers/generated/app.g.dart – Contains the Riverpod currentSSIDProvider definition used for state propagation.
  • lib/state.dart – Provides globalState.container, the interface used by the manager to write provider values.

Summary

  • The FlClash connectivity manager is a wrapper widget in lib/manager/connectivity_manager.dart that listens to Connectivity().onConnectivityChanged from the connectivity_plus plugin.
  • It automatically queries WifiSsidManager.instance.getSsid() when Wi-Fi is detected and updates currentSSIDProvider via globalState.container.
  • Non-Wi-Fi states clear the provider to null, ensuring accurate offline detection.
  • An optional onConnectivityChanged callback allows custom handling of raw connectivity results without modifying the manager's core logic.
  • The subscription is cancelled in dispose() to prevent memory leaks.
  • Riverpod providers ensure that any widget in the tree can react to network changes with minimal overhead.

Frequently Asked Questions

How does FlClash distinguish between Wi-Fi and mobile data connections?

The manager checks if the List<ConnectivityResult> emitted by the platform contains ConnectivityResult.wifi. If present, it treats the connection as Wi-Fi and attempts to fetch the SSID; otherwise, it sets the provider to null, indicating either mobile data, Ethernet, or no connection.

Why does the connectivity manager use Riverpod instead of setState?

Using currentSSIDProvider from Riverpod allows the manager to decouple network detection from UI rendering. Any widget in the application can watch the provider and rebuild automatically when the SSID changes, without the manager needing to know which widgets depend on the state.

Where is the Wi-Fi SSID actually stored in FlClash?

The SSID is stored in currentSSIDProvider, which is defined in lib/providers/generated/app.g.dart. The manager writes to this provider using globalState.container.read(currentSSIDProvider.notifier).value = ssid.

Can I use the connectivity manager without displaying the Wi-Fi name?

Yes. The manager supports an onConnectivityChanged callback that receives the raw List<ConnectivityResult> for every network change. You can implement this callback to handle connectivity logic—such as offline detection—while ignoring the SSID provider entirely.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →