Method Channel Implementation on Mobile and Desktop
Android + IOS + MacOS + Windows + Linux
Method channel allows flutter to communicate with the native platform. The channel message is enveloped in binary and sent with FIFO. Many packages has used this method to implement the features and it is not rare project required the native integration. Out of curiosity, I have searched the flutter.dev, it has provided the Android, IOS and Windows examples, but seems not have the MacOS and Linux example. This article has summarised the implementation of Android (Kotlin), IOS(Swift), MacOS(Swift), Windows(C++) and Linux(C) method channel setup in main project ( NOT the flutter plugin one).
Flutter(Dart)
The first thing is to define the channel name. Later on the native side, the channel name will be used to identify whether it is the same channel. Similarly the method name defined in the _platform.invokeMethod
will also used to identify what action it is calling. Since the channel communication is asynchronous, it requires await
the response later returned from native platform. Here is a simple state of stateful widget which only has one button to trigger the call.
class _MyHomePageState extends State<MyHomePage> {
/// TODO:[Flutter][1] Setup the MethodChannel
final MethodChannel _platform =
const MethodChannel('cbl.tool.flutter_platform_channel');
@override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(builder: (context) {
return Center(
child: ElevatedButton(
child: Text('Say hi to ${Platform.operatingSystem}!'),
onPressed: () async {
/// TODO:[Flutter][2] Submit call to the platform
String msg = await _platform.invokeMethod('shakeHand');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(msg),
),
);
}
},
),
);
}),
);
}
}
Android(Kotlin)
In android, what we need to do is to override the configureFlutterEngine
and use the FlutterEngine to Create a MethodChannel
by the binary messenger and channel name. The setMethodCallHandler
will call back if there is method called from flutter side. When the method name is shakeHand
, the channel will later greeting back the flutter side.
class MainActivity: FlutterActivity() {
private val channel = "cbl.tool.flutter_platform_channel"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// TODO:[Android] [1] override configureFlutterEngine and create MethodChannel
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channel).setMethodCallHandler {
call, result ->
if (call.method == "shakeHand") {
result.success("Hi from Android!");
}else{
result.notImplemented();
}
}
}
}
IOS(Swift)
For IOS, binaryMessenger
is retrieved from the FlutterViewController which will bind on the rootViewController
when app initialisation.
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// TODO:[IOS][1] Get the flutter root view controller for binary messenger
let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
// TODO:[IOS][2] Set the flutter channel
let flutterChannel = FlutterMethodChannel(name: "cbl.tool.flutter_platform_channel",
binaryMessenger: controller.binaryMessenger)
// TODO:[IOS][3] Set the channel method call handler
flutterChannel.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
if (call.method == "shakeHand") {
result(String("Hi from IOS!"))
} else {
result(FlutterMethodNotImplemented)
}
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
MacOS(Swift)
Similar to IOS, MacOS binaryMessenger
is retrieved from the FlutterViewController. Setup is basically same as the IOS one.
class MainFlutterWindow: NSWindow {
override func awakeFromNib() {
let flutterViewController = FlutterViewController.init()
let windowFrame = self.frame
self.contentViewController = flutterViewController
self.setFrame(windowFrame, display: true)
// TODO:[Mac][1] Set the flutter channel
let flutterChannel = FlutterMethodChannel(name: "cbl.tool.flutter_platform_channel",
binaryMessenger: flutterViewController.engine.binaryMessenger)
// TODO:[Mac][2] Set the channel method call handler
flutterChannel.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
if (call.method == "shakeHand") {
result(String("Hi from MacOS!"))
} else {
result(FlutterMethodNotImplemented)
}
})
RegisterGeneratedPlugins(registry: flutterViewController)
super.awakeFromNib()
}
}
Windows(C++)
Basically, similar to the MacOS one, using the flutterViewController to get the binary messenger. Be aware to import the header files.
#include "flutter_window.h"
#include <flutter/event_channel.h>
#include <flutter/event_sink.h>
#include <flutter/event_stream_handler_functions.h>
#include <flutter/method_channel.h>
#include <flutter/standard_method_codec.h>
#include <optional>
#include "flutter/generated_plugin_registrant.h"
bool FlutterWindow::OnCreate() {
if (!Win32Window::OnCreate()) {
return false;
}
RECT frame = GetClientArea();
// The size here must match the window dimensions to avoid unnecessary surface
// creation / destruction in the startup path.
flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
frame.right - frame.left, frame.bottom - frame.top, project_);
// Ensure that basic setup of the controller was successful.
if (!flutter_controller_->engine() || !flutter_controller_->view()) {
return false;
}
RegisterPlugins(flutter_controller_->engine());
// TODO:[Windows] [1] use flutter_controller get engin and then messenger
flutter::MethodChannel<> channel(
flutter_controller_->engine()->messenger(), "cbl.tool.flutter_platform_channel",
&flutter::StandardMethodCodec::GetInstance());
channel.SetMethodCallHandler(
[](const flutter::MethodCall<>& call,
std::unique_ptr<flutter::MethodResult<>> result) {
if (call.method_name() == "shakeHand") {
result->Success(flutter::EncodableValue("Hi from Windows!"));
}
else {
result->NotImplemented();
}
});
SetChildContent(flutter_controller_->view()->GetNativeWindow());
return true;
}
Linux(C)
For Linux, use flutter engine to get the binary messenger and then create a new method channel. The method_call_cb
is to handle the method call call back.
// TODO: [Linux][5] add handling to each method name
static void method_call_cb(FlMethodChannel* channel,
FlMethodCall* method_call,
gpointer user_data)
{
g_autoptr(FlMethodResponse) response = nullptr;
const gchar* method = fl_method_call_get_name(method_call);
if (strcmp(method, "shakeHand") == 0) {
response = FL_METHOD_RESPONSE(fl_method_success_response_new(fl_value_new_string("Hi from Linux!")));
} else {
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
}
fl_method_call_respond(method_call, response, nullptr);
}
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
...
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
// TODO: [Linux][1] Get engine from view
FlEngine *engine = fl_view_get_engine(view);
// TODO: [Linux][2] Get binary messenger
g_autoptr(FlBinaryMessenger) messenger = fl_engine_get_binary_messenger(engine);
// TODO: [Linux][3] Set channel
g_autoptr(FlMethodChannel) channel =
fl_method_channel_new(messenger,
"cbl.tool.flutter_platform_channel", // this is our channel name
FL_METHOD_CODEC(fl_standard_method_codec_new()));
// TODO: [Linux][4] Set channel call handler
fl_method_channel_set_method_call_handler(channel,
method_call_cb, g_object_ref(view), g_object_unref);
gtk_widget_grab_focus(GTK_WIDGET(view));
}
Value Mapping
Since the method channel uses the standard message codec which supports serialise and deserialise binary message to different types. This the table for the types mapping:
Dart | Kotlin | Java | Swift | Objective-C | C++ | C |
null | null | null | nil | nil (NSNull when nested) | EncodableValue() | FlValue() |
bool | Boolean | java.lang.Boolean | NSNumber(value: Bool) | NSNumber numberWithBool: | EncodableValue(bool) | FlValue(bool) |
int | Int | java.lang.Integer | NSNumber(value: Int32) | NSNumber numberWithInt: | EncodableValue(int32_t) | FlValue(int62_t) |
int, if 32 bits not enough | Long | java.lang.Long | NSNumber(value: Int) | NSNumber numberWithLong: | EncodableValue(int64_t) | FlValue(double) |
double | Double | java.lang.Double | NSNumber(value: Double) | NSNumber numberWithDouble: | EncodableValue(double) | FlValue(gchar*) |
String | String | java.lang.String | String | NSString | EncodableValue(std::string) | FlValue(uint8_t*) |
Uint8List | ByteArray | byte[] | FlutterStandardTypedData(bytes: Data) | FlutterStandardTypedData typedDataWithBytes: | EncodableValue(std::vector) | FlValue(int32_t*) |
Int32List | IntArray | int[] | FlutterStandardTypedData(int32: Data) | FlutterStandardTypedData typedDataWithInt32: | EncodableValue(std::vector) | FlValue(int64_t*) |
Int64List | LongArray | long[] | FlutterStandardTypedData(int64: Data) | FlutterStandardTypedData typedDataWithInt64: | EncodableValue(std::vector) | FlValue(int64_t*) |
Float32List | FloatArray | float[] | FlutterStandardTypedData(float32: Data) | FlutterStandardTypedData typedDataWithFloat32: | EncodableValue(std::vector) | FlValue(float*) |
Float64List | DoubleArray | double[] | FlutterStandardTypedData(float64: Data) | FlutterStandardTypedData typedDataWithFloat64: | EncodableValue(std::vector) | FlValue(double*) |
List | List | java.util.ArrayList | Array | NSArray | EncodableValue(std::vector) | FlValue(FlValue) |
Map | HashMap | java.util.HashMap | Dictionary | NSArray | EncodableValue(std::map) | FlValue(FlValue, FlValue) |
Demo Project
Reference
flutter.dev: Writing custom platform-specific code
url_launcher repo for different platform implementation reference