CoreController Singleton in FlClash: The Bridge Between Flutter and Go
The CoreController singleton serves as the sole communication bridge between FlClash's Flutter UI and the Clash Meta core written in Go, instantiating platform-specific handlers (FFI for Android, sockets for Desktop) and exposing a unified Dart API for all core operations.
FlClash is a cross-platform proxy client that combines a Flutter frontend with the Clash Meta core written in Go. The CoreController singleton manages all Flutter-Go communication, ensuring the mobile and desktop applications interact with the underlying Go engine through a consistent interface regardless of whether the app runs on Android or Desktop operating systems.
Singleton Pattern and Global Access
The CoreController is implemented as a strict singleton to prevent multiple connections to the core process. In lib/core/controller.dart, the class defines a private static field _instance (lines 14-16) that holds the single instance of the controller.
The factory constructor factory CoreController() (lines 34-37) ensures that the first call creates the internal instance via CoreController._internal(), while all subsequent calls return the same object. For convenient global access throughout the codebase, FlClash exposes a top-level variable coreController (line 87) that holds this singleton instance.
Platform-Specific Handler Selection
When the singleton is instantiated through CoreController._internal (lines 18-23), it performs platform detection to select the appropriate transport mechanism for Flutter-Go communication.
The implementation checks the platform and assigns the handler:
- Android: Uses
CoreLib(an FFI wrapper) accessed viacoreLib!(lines 19-21) - Desktop: Uses
CoreService(a socket/pipe client) accessed viacoreService!(lines 22-23)
Both handlers implement the abstract mixin CoreHandlerInterface defined in lib/core/interface.dart (lines 77-84). This interface defines the complete set of RPC-style methods that the Go core exposes, including init, shutdown, getProxies, changeProxy, and runtime metrics methods.
Unified API Surface for Core Operations
CoreController forwards every public method call to the selected handler's _interface, keeping the rest of the Flutter codebase agnostic of the underlying transport mechanism.
The controller exposes high-level methods that map directly to Go core functionality:
- Initialization and shutdown:
init(),shutdown()→_interface.init(...),_interface.shutdown(...) - Configuration validation:
validateConfig()→_interface.validateConfig(...) - Proxy management:
getProxiesGroups(),changeProxy()→_interface.getProxies(),_interface.changeProxy(...) - Runtime metrics:
getTraffic(),getTotalTraffic(),getMemory()→ corresponding handler calls - Event-driven actions:
startLog(),stopLog(),requestGc(),crash()→ handler methods
This architecture ensures that UI components and business logic never need to know whether they are communicating via FFI or sockets.
Lifecycle Coordination and Connection State
Beyond method forwarding, the controller manages the core-process lifecycle and connection state. It exposes the underlying handler's completer through a getter bool get isCompleted (line 39), allowing other components to verify whether the core connection is fully established before issuing commands.
This is critical for startup sequences where the UI must wait for the Go core to initialize completely before requesting proxy lists or traffic statistics.
Implementation Files and Architecture
The Flutter-Go communication architecture spans four key files in the lib/core/ directory:
| File | Role |
|---|---|
lib/core/controller.dart |
Singleton that selects the correct handler and forwards all calls |
lib/core/interface.dart |
Defines CoreHandlerInterface with all RPC method signatures |
lib/core/lib.dart |
Android FFI implementation (CoreLib) |
lib/core/service.dart |
Desktop socket/pipe implementation (CoreService) |
Practical Usage Examples
Below are common patterns for interacting with the Go core through the CoreController singleton:
// Initialize the core with the current configuration version
await coreController.init(appVersion);
// Validate a user-provided configuration file
final validationMessage = await coreController.validateConfig('/path/to/config.yaml');
// Switch the active proxy
await coreController.changeProxy(ChangeProxyParams(
name: 'Proxy-Group-1',
proxy: '🇺🇸 US-Node',
));
// Retrieve traffic statistics for UI display
final traffic = await coreController.getTraffic(onlyStatisticsProxy: true);
Summary
- The
CoreControllersingleton in FlClash (lib/core/controller.dart) is the exclusive bridge between Flutter and the Clash Meta Go core. - It implements the singleton pattern using a private
_instancefield and factory constructor to ensure only one core connection exists. - The controller automatically selects between
CoreLib(FFI for Android) andCoreService(sockets for Desktop) during initialization. - All RPC-style methods—initialization, proxy management, traffic metrics, and lifecycle control—are unified through the
CoreHandlerInterfaceabstraction. - The
isCompletedgetter exposes the connection state's completer, allowing components to synchronize with the core's readiness.
Frequently Asked Questions
What is the CoreController singleton in FlClash?
The CoreController is a singleton class in lib/core/controller.dart that serves as the single point of entry for all communication between FlClash's Flutter UI and the Clash Meta core written in Go. It ensures that only one connection to the core exists throughout the application lifecycle and provides a platform-agnostic API for calling Go functions from Dart.
How does FlClash handle different platforms in CoreController?
During instantiation via CoreController._internal (lines 18-23), the controller checks the current platform and selects the appropriate handler implementation. For Android, it uses CoreLib (an FFI wrapper accessed via coreLib!), while desktop platforms use CoreService (a socket/pipe client accessed via coreService!). Both implement the same CoreHandlerInterface, ensuring consistent behavior across platforms.
What core operations can be performed through CoreController?
The controller exposes the full range of Clash Meta core operations defined in CoreHandlerInterface, including init and shutdown for lifecycle management, validateConfig for configuration checking, getProxies and changeProxy for proxy management, and telemetry methods like getTraffic, getTotalTraffic, and getMemory. It also supports logging controls (startLog, stopLog), garbage collection requests (requestGc), and crash reporting (crash).
When should I check the isCompleted property?
You should check coreController.isCompleted (which exposes the underlying handler's completer state at line 39) before issuing commands that require an active core connection, such as fetching proxy lists or traffic statistics. This boolean getter indicates whether the connection to the Go core is fully established, preventing errors from premature API calls during application startup.
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 →