How FlClash Collects and Displays Real-Time Traffic Statistics: A Complete Technical Guide
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:
- Reads the "only-statistics-proxy" configuration setting to determine filtering behavior
- Awaits results from
CoreController.getTraffic()andCoreController.getTotalTraffic() - 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.getTrafficandgetTotalTrafficfetch raw byte counters from the Clash Meta core via platform channels. - Polling Loop:
SetupActioncreates a one-second timer that invokesupdateTraffic()to refresh statistics continuously while the core runs. - State Containers:
trafficsProvidermaintains a fixed-size history buffer for charting, whiletotalTrafficProvidertracks cumulative totals. - Formatting Layer:
NumExt.trafficextensions 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".
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →