# How FlClash Collects and Displays Real-Time Traffic Statistics: A Complete Technical Guide

> Learn how FlClash collects and displays real-time traffic statistics. Understand the technical process from polling Clash Meta core to updating your dashboard and system tray.

- Repository: [chen08209/FlClash](https://github.com/chen08209/FlClash)
- Tags: how-to-guide
- Published: 2026-05-31

---

**FlClash polls the native Clash Meta core every second to fetch raw byte counters, converts them into human-readable units, and propagates the data through Riverpod providers to update the dashboard, speed charts, and system tray in real time.**

FlClash is a Flutter-based GUI client for Clash Meta that provides users with detailed networking insights. Understanding how the application collects and renders **real-time traffic statistics** requires examining the interplay between the Go-based core and the Dart frontend. The implementation relies on periodic polling, immutable state containers, and reactive UI bindings.

## Core-Side Traffic Collection

The foundation of FlClash's statistics system lies in the native Clash Meta core, which maintains per-proxy byte counters internally. The Dart layer communicates with these counters through a structured interface defined in `lib/core/controller.dart`.

The **`CoreController.getTraffic`** and **`CoreController.getTotalTraffic`** methods serve as the primary entry points. These functions invoke `CoreInterface.getTraffic`, which returns a JSON payload containing `up` and `down` fields representing upload and download bytes respectively. The controller abstracts the platform channel communication, exposing clean async methods that the Flutter application can consume without handling raw FFI or platform-specific code.

## Periodic Polling Mechanism

To ensure the UI reflects live network conditions, FlClash implements a polling loop that activates when the VPN core starts. In `lib/providers/action.dart`, the `SetupAction._handleStart()` method initializes a `Timer.periodic` with a one-second interval【/cache/repos/github.com/chen08209/FlClash/main/lib/providers/action.dart#L39-L50】.

Each tick triggers **`CommonAction.updateTraffic()`**, which performs the following sequence:

1. Reads the *"only-statistics-proxy"* configuration setting to determine filtering behavior
2. Awaits results from `CoreController.getTraffic()` and `CoreController.getTotalTraffic()`
3. Writes the processed data to Riverpod state providers【/cache/repos/github.com/chen08209/FlClash/main/lib/providers/action.dart#L57-L65】

This pull-based architecture ensures that traffic data remains synchronized with the native core's internal state while preventing unnecessary background computation when the core is inactive.

## State Management with Riverpod

FlClash leverages two distinct Riverpod providers to separate historical tracking from aggregate totals, both defined in `lib/providers/generated/app.g.dart`.

The **`trafficsProvider`** maintains a `FixedList<Traffic>`—a fixed-capacity collection that automatically discards the oldest sample when new data arrives【/cache/repos/github.com/chen08209/FlClash/main/lib/providers/generated/app.g.dart#L351-L360】. This circular buffer enables the speed chart to display a sliding window of recent network activity without unbounded memory growth.

Conversely, **`totalTrafficProvider`** holds a single `Traffic` instance representing the cumulative sum of all proxy traffic since the core started【/cache/repos/github.com/chen08209/FlClash/main/lib/providers/generated/app.g.dart#L77-L85】. Any widget accessing these providers via `ref.watch()` automatically rebuilds when the timer updates the underlying values.

## Data Formatting and Human-Readable Conversion

Raw byte counts retrieved from the core require transformation before display. The `Traffic` data class in `lib/models/common.dart` stores `up` and `down` as primitive numeric values【/lib/models/common.dart#L56-L62】, serving as a transport layer between the core and presentation logic.

Formatting logic resides in `lib/common/num.dart` through the **`NumExt.traffic`** and **`NumExt.shortTraffic`** extension methods【/lib/common/num.dart#L29-L41】【/lib/common/num.dart#L43-L55】. These utilities convert byte values into IEC-compliant units (B, KB, MB, GB), returning formatted strings suitable for UI rendering. The `traffic` property produces full labels like "12.5 MB/s", while `shortTraffic` generates compact representations such as "12.5M" for space-constrained widgets.

## UI Components and Real-Time Rendering

The reactive architecture ensures that dashboard widgets update automatically when the polling cycle modifies provider state.

**TrafficUsage** (located in `lib/views/dashboard/widgets/traffic_usage.dart#L65-L99`) consumes `totalTrafficProvider` to render the donut-chart card showing aggregate upload and download volumes. The widget applies the `NumExt.traffic` formatter to present cumulative totals in the appropriate magnitude.

**NetworkSpeed** (in `lib/views/dashboard/widgets/network_speed.dart#L50-L86`) subscribes to `trafficsProvider.list` to plot the historical speed curve. By iterating over the fixed-length samples list, this component calculates per-second transfer rates and displays both the line chart and current speed text.

Beyond the main window, the system tray integration also participates in real-time updates. The `TrayTitleState` class in `lib/models/state.dart#L42-L45` derives its `traffic` field from `totalTrafficProvider`, enabling the tray icon title to display live speeds in the format "↑ X/s ↓ Y/s" when the user enables this option.

## Summary

- **Core Interface**: `CoreController.getTraffic` and `getTotalTraffic` fetch raw byte counters from the Clash Meta core via platform channels.
- **Polling Loop**: `SetupAction` creates a one-second timer that invokes `updateTraffic()` to refresh statistics continuously while the core runs.
- **State Containers**: `trafficsProvider` maintains a fixed-size history buffer for charting, while `totalTrafficProvider` tracks cumulative totals.
- **Formatting Layer**: `NumExt.traffic` extensions convert raw bytes to human-readable units (KB, MB, GB) before display.
- **Reactive UI**: Dashboard cards and system tray widgets use `ref.watch()` to rebuild automatically when new traffic data arrives.

## Frequently Asked Questions

### How often does FlClash update traffic statistics?

FlClash polls the native Clash Meta core every second via a `Timer.periodic` initialized in `SetupAction._handleStart()`. This one-second interval balances real-time accuracy with performance overhead, ensuring the dashboard and speed charts reflect current network conditions without excessive battery drain.

### Where does FlClash store historical speed data for the network chart?

Historical samples reside in `trafficsProvider`, which utilizes a `FixedList<Traffic>` data structure with a predetermined capacity. When the list reaches its limit, the oldest entry is automatically discarded to make room for new measurements, creating a sliding window effect ideal for time-series visualization.

### Can I access traffic statistics programmatically in custom widgets?

Yes. Any widget can consume real-time traffic data by using Riverpod's `ref.watch()` method with either `totalTrafficProvider` for aggregate totals or `trafficsProvider` for historical samples. The `Traffic` model exposes raw `up` and `down` values, while the `NumExt.traffic` extension provides ready-to-display formatted strings.

### Why does FlClash use polling instead of pushing updates from the core?

FlClash employs a pull-based architecture where the Flutter layer periodically requests data from the native core. This approach decouples the UI from the core's internal event loop, prevents memory leaks from unhandled stream subscriptions, and allows the application to control update frequency through configuration settings like *"only-statistics-proxy"*.