How FlClash Integrates the ClashMeta Core with Flutter on Android and Desktop

FlClash bridges its Flutter UI to the native ClashMeta Go core using JNI on Android and JSON-over-IPC on desktop, exposing both platforms through a unified CoreController API.

FlClash is an open-source proxy client maintained by chen08209 that combines the ClashMeta core with a cross-platform Flutter interface. The integration relies on two distinct native communication patterns to deliver the same Dart-level functionality across mobile and desktop operating systems.

Platform-Specific Integration Architecture

Android FFI and JNI Layer

On Android, the ClashMeta core compiles into a C shared library (libclash.so) using CGO. The Flutter layer communicates with this native library through a MethodChannel (com.follow.clash/service) that forwards calls to Kotlin/Java code, which then invokes the Go functions via JNI.

The entry point for these calls resides in android/core/src/main/java/com/follow/clash/core/Core.kt, where external declarations map to Go-exported C functions such as init and invokeAction. The Go side exposes these functions in core/lib.go and core/main_cgo.go, allowing the Dart layer to execute core actions within the same process.

Desktop IPC Process Model

For macOS, Linux, and Windows, FlClash spawns the ClashMeta core as a separate process. Flutter communicates through a JSON-over-IPC channel using Unix domain sockets on Unix-like systems and named pipes on Windows.

The CoreService class launches the Go binary (appPath.corePath) with the IPC address as an argument via Process.start. The IPCCoreTransport class in lib/core/transport.dart manages the socket or pipe connection, sending JSON-encoded Action objects and receiving ActionResult responses. The Go process, implemented in core/hub.go, reads these messages and dispatches them to the appropriate ClashMeta handlers.

The CoreController Abstraction

At the heart of the integration sits CoreController, a singleton defined in lib/core/controller.dart that abstracts platform differences behind a common interface. At runtime, it selects the appropriate implementation based on the host operating system.

if (system.isAndroid) {
  _interface = coreLib!;          // Android FFI path
} else {
  _interface = coreService!;      // Desktop IPC path
}

Both CoreLib (Android) and CoreService (desktop) implement CoreHandlerInterface, ensuring that methods like init(), getProxiesGroups(), and changeProxy() behave identically regardless of the underlying transport mechanism.

Android Implementation Details

The Android pathway follows a four-step delegation chain from Dart to Go:

  1. Flutter MethodChannel – The Service class in lib/plugins/service.dart sends a JSON Action through the com.follow.clash/service channel.
  2. Kotlin JNI Bridge – The Core object in android/core/src/main/java/com/follow/clash/core/Core.kt receives the JSON and calls the native invokeAction C function.
  3. Go Shared Library – The compiled libclash.so (built from core/lib.go) dispatches the call to Go handlers such as handleGetProxies.
  4. Result Propagation – The native code returns a JSON string, which Kotlin passes back through the MethodChannel to resolve the original Dart Future.

Desktop Implementation Details

The desktop flow establishes a persistent IPC connection between the Flutter UI and the Go binary:

  1. Process SpawningCoreService.start() in lib/core/service.dart launches the Go executable with the IPC address as a command-line argument.
  2. Transport InitializationIPCCoreTransport opens the socket or pipe and signals readiness to the Flutter layer.
  3. Action InvocationCoreService.invoke serializes the Action object to JSON and writes it to the transport.
  4. Go Processing – The Go process reads the message in core/hub.go, executes the requested action (e.g., handleChangeProxy), and writes an ActionResult back to the socket.
  5. Dart Completion – The transport reads the response and completes the awaiting Dart Future with the result.

Practical Code Examples

Initialize the core and preload native resources:

await coreController.init(version);
await coreController.preload();

Fetch proxy groups with latency sorting:

final groups = await coreController.getProxiesGroups(
  sortType: ProxiesSortType.latency,
  delayMap: {},
  selectedMap: {},
  defaultTestUrl: 'https://www.gstatic.com/generate_204',
);

Change the active proxy selection:

await coreController.changeProxy(
  ChangeProxyParams(groupName: 'Proxy', proxyName: 'US-01'),
);

Gracefully shutdown the core:

await coreController.shutdown(isUser: true);

Summary

  • FlClash uses JNI/CGO on Android to load libclash.so directly within the app process, satisfying platform restrictions for VPN services.
  • Desktop platforms use JSON-over-IPC to communicate with a separate Go process via sockets or named pipes, simplifying sandboxing and privilege separation.
  • The CoreController singleton in lib/core/controller.dart provides a unified Dart API that transparently selects the correct transport mechanism at runtime.
  • Key source files include android/core/src/main/java/com/follow/clash/core/Core.kt for Android JNI, core/lib.go for Go exports, and lib/core/transport.dart for desktop IPC management.

Frequently Asked Questions

Why does FlClash use different integration methods for Android and Desktop?

Android requires in-process execution to satisfy platform restrictions and allow the VPN/TUN service to be driven directly via the FFI layer. Desktop systems can safely launch the core as a stand-alone process, simplifying sandboxing and allowing the core to run with its own privileges, particularly on Windows where helper services may be used.

How does the Dart layer handle platform detection?

The CoreController singleton checks system.isAndroid during initialization. If true, it instantiates CoreLib for FFI communication; otherwise, it instantiates CoreService for IPC communication. Both classes implement the same CoreHandlerInterface, ensuring the rest of the application remains platform-agnostic.

Can the FlClash core run independently of the Flutter UI on desktop?

Yes. On desktop platforms, the core runs as a separate Go binary spawned by CoreService via Process.start. It listens on a Unix domain socket or named pipe and can persist independently until explicitly shut down through the IPC channel.

What is the role of the Action and ActionResult classes in FlClash?

These classes define the JSON message protocol used across both platforms. Action encapsulates method calls and parameters sent from Dart to Go, while ActionResult wraps return values and errors sent back to Dart. This unified message format allows the same Go handlers in core/hub.go to service requests from both Android (via JNI delegation) and desktop (via IPC).

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 →