Network-Reachability

Banner

An advanced network monitoring and resilience library for Flutter, powered by a high-performance Rust core.
Go beyond simple connectivity checks. Understand the quality, stability, and security of your user's network.

Why?Key FeaturesInstallationBasic UsageAdvanced UsageContributing


🤔 Why Choose Network-Reachability?

Stop guessing. Start knowing. In a world where a "Connected" status is often a lie, your app needs more than a boolean. It needs a pulse.

Most network libraries tell you if you're connected or disconnected. In the real world, this is simply not enough. A user might be "connected" but on a network so slow it's unusable, behind a login page (Captive Portal), or on an insecure public WiFi exposing your data.

📊 How we compare

Feature connectivity_plus internet_checker Network-Reachability
Connection Type ✅ WiFi, Cellular, Ethernet, etc.
Internet Verification ✅ Deep Multi-Target Probing
Performance Engine Dart/Native Dart 🚀 High-Perf Rust Native Core
UI Responsiveness ⚠️ ⚡ Zero UI-Thread Blocking
Detailed Metrics 📈 Latency, Jitter, Packet Loss
Security Suite 🛡️ VPN, Proxy & DNS Hijack
Resilience Logic 🔋 Circuit Breaker (Open/Half-Open)
Request Coalescing 🤝 Thundering Herd Protection
Battery Management 🔋 Adaptive Lifecycle Polling
Action Protection 🔒 guard() Smart Wrapper

✨ Key Features

  • 🚀 High-Performance Rust Core: All heavy lifting—DNS resolution, multi-protocol pings, and quality calculations—happens in a native Rust engine. This ensures sub-millisecond precision without ever dropping a frame in your Flutter UI.

  • 🔒 The guard() Pattern: Wrap your API calls in a smart safety net.

    • Automatically verifies quality before execution.
    • Prevents "Ghost Requests" on unstable networks.
    • Built-in CircuitBreaker integration.
  • 🔋 Battery-Aware Intelligence:

    • Adaptive Polling: Automatically slows down checks on Excellent connections to save battery, but speeds up during Poor phases to detect recovery instantly.
    • Lifecycle Aware: Automatically pauses all network activity when the app goes to background.
  • 🤝 Thundering Herd Protection: Built-in request coalescing ensures that if 100 widgets request a network check at the same microsecond, only one underlying probe is actually sent.

  • 🛡️ Enterprise-Grade Security:

    • DNS Hijack Detection: Detects if your ISP or a malicious actor is redirecting your traffic.
    • VPN/Proxy Shield: Identify anonymized connections to protect sensitive data.
    • Captive Portal Engine: Specifically identifies public WiFi login pages that block internet access.
  • 📈 Stability Scoring: Not just a "Yes/No" status. We calculate a StabilityScore (0-100) based on jitter (latency variance) and packet loss, helping you decide if the connection is reliable enough for heavy tasks.


📦 Installation

Tip

Don't worry about the "Rust Core"! Adding Network-Reachability to your project is designed to be as simple as adding any other Flutter package. While it uses a high-performance Rust engine, you don't need to be a Rust expert or manage complex builds manually. You just install the language once, and the library handles all the heavy lifting, compiling itself automatically for whatever platform (Android, iOS, etc.) or architecture you are targeting.

1. Prerequisites (The Rust Toolchain)

Since this library uses Cargokit to bridge Flutter and Rust, you need the Rust compiler installed on your development machine.

  • Windows: Download and run rustup-init.exe.
  • macOS / Linux: Run the following command in your terminal:
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
    

Important

Once Rust is installed, Cargokit will automatically detect your Flutter build target and compile the Rust core into a high-performance native shared library specifically for that OS and architecture. You only need to set this up once!

2. Add the Dependency

Add the package to your pubspec.yaml:

dependencies:
  network_reachability: ^0.0.1

Then, fetch the package:

flutter pub get

3. Platform Configuration

Important

You must add the following permissions to your application to allow it to monitor network status and quality. Without these, the library cannot accurately detect the network type or perform deep probes.

Android

Add these permissions to your android/app/src/main/AndroidManifest.xml:

<manifest ...>
    <!-- Required to make network requests -->
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- Required to check the type of connection (WiFi, Cellular, etc.) -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    ...
</manifest>

iOS

Standard internet checks don't require explicit permissions.

Caution

If your configuration probes local network targets (e.g., a local server or IoT device), you must add the following to your ios/Runner/Info.plist to avoid system blocks:

<key>NSLocalNetworkUsageDescription</key>
<string>This app needs access to the local network to monitor connectivity stability and quality.</string>

🚀 Basic Usage

1. Initialization

Initialize the library in your main() function.

Important

You must call RustLib.init() before NetworkReachability.init(). This ensures the native Rust engine is loaded into memory correctly.

import 'package:flutter/material.dart';
import 'package:network_reachability/network_reachability.dart';
import 'package:network_reachability/core/rust/frb_generated.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // Initialize the Rust library bindings.
  await RustLib.init();
  // Initialize Network-Reachability with a default or custom configuration.
  // this uses a default configuration.
  await NetworkReachability.init();
  runApp(const MyApp());
}

2. Protecting Network Calls with guard()

Tip

Using guard() is the best way to handle intermittent connectivity. It automatically checks the network just before your action runs, preventing unnecessary server hits when the connection is failing.

Future<void> fetchSensitiveData() async {
  try {
    // Wrap your API call with the guard.
    final data = await NetworkReachability.instance.guard(
      // The action to perform ONLY if checks pass.
      action: () => myApi.fetchImportantData(),
      // Optional: Require a minimum quality for this specific action.
      minQuality: ConnectionQuality.good,
    );
    print('Data fetched successfully: $data');

  } on PoorConnectionException catch (e) {
    // Thrown if quality is below 'good'.
    print('Could not fetch data: The connection is too slow or unstable. Details: ${e.message}');

  } on SecurityException catch (e) {
    // Thrown if a security policy is violated (e.g., VPN detected).
    print('Action blocked due to a security risk: ${e.message}');

  } on CircuitBreakerOpenException catch (e) {
    // Thrown if the backend is known to be unstable.
    print('Our servers are temporarily unavailable. Please try again later. Details: ${e.message}');
  }
}

Caution

If the CircuitBreaker opens, all subsequent guard() calls for that target will fail immediately with a CircuitBreakerOpenException until the cooldown period expires.

3. Monitoring Status Changes

void listenToNetworkChanges() {
  final subscription = NetworkReachability.instance.onStatusChange.listen((status) {
    // Note: The stream provides a lightweight `NetworkStatus` object.
    // For a full report, you would call `check()` inside the listener.
    print('Network status updated: ${status.isConnected ? 'Connected' : 'Disconnected'} - Quality: ${status.quality.name}');
    print('Latency: ${status.latencyStats.latencyMs}ms');
    print('Jitter: ${status.latencyStats.jitterMs}ms');
    print('Packet Loss: ${status.latencyStats.packetLossPercent}%');
    print('Stability Score: ${status.latencyStats.stabilityScore}/100');
    // Update your UI based on the new status
  });

  // Don't forget to cancel the subscription in your widget's dispose() method.
}

🔬 Advanced Usage

Custom Configuration

Tip

You can create multiple NetworkTarget objects to monitor different microservices or fallback endpoints.

import 'package:network_reachability/network_reachability.dart';

Future<void> initializeWithCustomConfig() async {
  final config = await NetworkConfiguration.default_(); // Get the default config

  final customConfig = NetworkConfiguration(
    targets: [
      NetworkTarget(
        label: 'my-backend-primary',
        host: 'api.mydomain.com',
        port: 443,
        protocol: TargetProtocol.tcp,
        timeoutMs: BigInt.from(2000),
        isEssential: true, // This target affects the circuit breaker if it fails the app goes offline
        priority: 1,
      ),
    ],
    checkIntervalMs: BigInt.from(15000), // 15 seconds
    cacheValidityMs: BigInt.from(2000), // 2 seconds cache
    // Defines the latency thresholds (in milliseconds) used to determine [ConnectionQuality].
    qualityThreshold: QualityThresholds(
      excellent: BigInt.from(50),
      great: BigInt.from(100),
      good: BigInt.from(150),
      moderate: BigInt.from(250),
      poor: BigInt.from(500),
    ),
    // Configuration for security-related checks.
    security: SecurityConfig(
      blockVpn: true,
      detectDnsHijack: true,
    ),
    // Configuration for the circuit breaker and resilience
    resilience: ResilienceConfig(
      // first to respond wins
      strategy: CheckStrategy.race,

      // The number of consecutive failures of essential targets before the circuit breaker opens.
      circuitBreakerThreshold: 3,

      // Cooldown period before the circuit breaker transitions to Half-Open.
      circuitBreakerCooldownMs: BigInt.from(60000), // 1 minute

      // Number of samples to take for jitter and stability analysis.
      numJitterSamples: 5,

      // The percentage of mean latency that the standard deviation must exceed to be considered high jitter.
      jitterThresholdPercent: 0.2,

      // If the calculated stability score is less than this value, the quality considered 'Unstable'.
      stabilityThershold: 80,

      // The packet loss percentage above which the connection is marked as 'Unstable'.
      criticalPacketLossPrecent: 5.0,
    ),
  );

  await NetworkReachability.init(config: customConfig);
}

Direct Probe Access

Tip

Direct probes are useful for "pre-flight" checks, like checking for a Captive Portal before showing a login button.

// Check if the user is behind a WiFi login page
final captiveStatus = await NetworkReachability.instance.checkForCaptivePortal(
  timeoutMs: BigInt.from(5000),
);
if (captiveStatus.isCaptivePortal) {
  print('User may need to log in to the network at ${captiveStatus.redirectUrl}');
}

// Check for DNS tampering
final isHijacked = await NetworkReachability.instance.detectDnsHijacking(
  domain: 'my-api.com',
);
if (isHijacked) {
  print('Warning: Potential DNS hijacking detected!');
}

🤝 Contributing

Contributions are welcome! Here’s how to get started:

  1. Fork the repository.
  2. Create a new branch: git checkout -b feature/YourFeature
  3. Commit your changes: git commit -m "Add amazing feature"
  4. Push to your branch: git push origin feature/YourFeature
  5. Open a pull request.

💡 Please read our Contributing Guidelines and open an issue first for major feature ideas or changes.


📜 License

This project is licensed under the GPL-3.0 License. See the LICENSE file for full details.

Made with ❤️ by MostafaSensei106