Flutter and Native communication MethodChannel

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

  1. [native] use methodchannel ා setmethodcallhandler to register callback
  2. [fluent] initiates asynchronous call through methodchannel ා invokemethod
  3. [native] call the native method to return Result through Result ා success, and return error in case of error
  4. [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

Published 26 original articles, won praise 1, visited 740
Private letter follow

Keywords: Android

Added by sullyman on Mon, 10 Feb 2020 14:07:23 +0200