flutter can communicate with native to help us use the capabilities provided by native. Communication is bi-directional. We can call dart code from native layer and native code from fluent layer. We need to use Platform Channels APIs for communication, mainly including the following three types:
- MethodChanel: used for method invocation
- EventChannel: used for communication of event streams
- MessageChannel: used to pass string and semi-structured information
The most commonly used one is MethodChanel. Using MethodChanel is very similar to using JNI in Android, but the use of MethodChanel will be simpler. Compared with the synchronous call of JNI, the call of MethodChanel is asynchronous:
1. Basic principles of methodchanel
It can be seen from the flitter architecture diagram that the communication between flitter and native occurs between the Framework and Engine, and the method channel will exchange data with the Engine in the form of BinaryMessage in framewrap. There is no introduction to BinaryMessage here. It mainly focuses on the use of MethodChannel. Let's first look at the basic process used by MethodChanel:
Fluent calls native
- [native] use methodchannel ා setmethodcallhandler to register callback
- [fluent] initiates asynchronous call through methodchannel ා invokemethod
- [native] call the native method to return Result through Result ා success, and return error in case of error
- [fluent] received the Result returned by native
native calls flutter
It is exactly the same as the order in which flutter calls native, except that the roles of [native] and [flutter] are reversed
2. Code implementation
Fluent calls native
First, the following functions are implemented in the fluent:
- Create a MethodChannel and register the channel name, generally using "package name / identity" as the channel name
- An asynchronous call is initiated through invokeMethod, which takes two parameters:
- Method: the name of the native method you want to call
- Arguments: parameters required by the naturalie method. When multiple arguments are involved, they need to be specified in the form of map
import 'package:flutter/services.dart'; class _MyHomePageState extends State<MyHomePage> { static const MethodChannel _channel = const MethodChannel('com.example.methodchannel/interop'); static Future<dynamic> get _list async { final Map params = <String, dynamic> { 'name': 'my name is hoge', 'age': 25, }; final List<dynamic> list = await _channel.invokeMethod('getList', params); return list; } @override initState() { super.initState(); // Dart -> Platforms _list.then((value) => print(value)); }
Secondly, the following functions are implemented in native (android)
- Create a MethodChannel using the same registration string as the fluent
- Implement and set the methodcallhandler, in which the parameters from the flitter are passed
- Return the result to flutter through result
class MainActivity: FlutterActivity() { companion object { private const val CHANNEL = "com.example.methodchannel/interop" private const val METHOD_GET_LIST = "getList" } private lateinit var channel: MethodChannel override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) channel.setMethodCallHandler { methodCall: MethodCall, result: MethodChannel.Result -> if (methodCall.method == METHOD_GET_LIST) { val name = methodCall.argument<String>("name").toString() val age = methodCall.argument<Int>("age") Log.d("Android", "name = ${name}, age = $age") val list = listOf("data0", "data1", "data2") result.success(list) } else result.notImplemented() } }
Because the result is returned asynchronously, you can either return the result through result.success in MethodCallHandler like the above code, or save the reference of result first, and then call success at a later point in time. However, you need to pay special attention to that whenever you call result.success, you must ensure that it is done in UI thread:
@UiThread void success(@Nullable Object result)
native calls flutter
The code implementation of android calling flutter is similar to that of flutter calling android, except that all calls should be made in the UI thread.
First, implement the code of android:
channel.invokeMethod("callMe", listOf("a", "b"), object : MethodChannel.Result { override fun success(result: Any?) { Log.d("Android", "result = $result") } override fun error(errorCode: String?, errorMessage: String?, errorDetails: Any?) { Log.d("Android", "$errorCode, $errorMessage, $errorDetails") } override fun notImplemented() { Log.d("Android", "notImplemented") } }) result.success(null)
The flutte part mainly implements the registration of MethodCallHandler:
Future<dynamic> _platformCallHandler(MethodCall call) async { switch (call.method) { case 'callMe': print('call callMe : arguments = ${call.arguments}'); return Future.value('called from platform!'); //return Future.error('error message!!'); default: print('Unknowm method ${call.method}'); throw MissingPluginException(); break; } } @override initState() { super.initState(); // Platforms -> Dart _channel.setMethodCallHandler(_platformCallHandler); }
Reference resources: https://flutter.dev/docs/development/platform-integration/platform-channels