preface

As a mixed development of Flutter, some interaction with the native terminal is inevitable. For example, when the native system sensor is called and the network framework of the native terminal is used for data request, the method that Flutter calls Android and Android calls Flutter is used. Platform Channels

Platform Channels

A Flutter passes messages between a Channel and a client, as shown in the following figure:

image.png

This is a MethodChannel for message passing between a Flutter and a client. MethodChannel is one of the Platform Channels. A Flutter has three types of communication:

Method Invocation BasicMessageChannel: Method Invocation usually used to call a method in native invocation EventChannel: Communication for Event Streams. There are monitoring functions, such as power changes that push data directly to the flutter terminal.Copy the code

To ensure that the UI responds, messages passed through Platform Channels are asynchronous.

For more information on channel principles, see this article: Channel Principles

Platform Channels to use

1. MethodChannel use

Native client writing (Using Android as an example)

Start by defining a way to get your phone’s battery

private int getBatteryLevel() {
        return 90;
    }
Copy the code

This function is the method to be called on Flutter, and you need to set up this MethodChannel with MethodChannel.

Let’s start with a new method that initializes a MethodChannel

private String METHOD_CHANNEL = "common.flutter/battery";
private String GET_BATTERY_LEVEL = "getBatteryLevel";
private MethodChannel methodChannel;

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GeneratedPluginRegistrant.registerWith(this);
        initMethodChannel();
        getFlutterView().postDelayed(() ->
            methodChannel.invokeMethod("get_message", null, new MethodChannel.Result() {
                @Override
                public void success(@Nullable Object o) {
                    Log.d(TAG, "get_message:" + o.toString());
                }

                @Override
                public void error(String s, @Nullable String s1, @Nullable Object o) {

                }

                @Override
                public void notImplemented() {}}), 5000); } private voidinitMethodChannel() {
        methodChannel = new MethodChannel(getFlutterView(), METHOD_CHANNEL);
        methodChannel.setMethodCallHandler(
                (methodCall, result) -> {
                    if (methodCall.method.equals(GET_BATTERY_LEVEL)) {
                        int batteryLevel = getBatteryLevel();

                        if(batteryLevel ! = -1) { result.success(batteryLevel); }else {
                            result.error("UNAVAILABLE"."Battery level not available.", null); }}else{ result.notImplemented(); }}); } private intgetBatteryLevel() {
        return 90;
    }

Copy the code

Method_channels are used to identify interactions with a flutter. Since there are usually multiple channels, they need to be unique within the app

Methodchannels are stored in a Map with the channel name Key. So if you set two channels with the same name, only the latter one will take effect.

OnMethodCall takes two parameters, and the onMethodCall contains the name of the method to call and its parameters. Result is the return value to Flutter. The method name is set by the client and the Flutter. Methodcall. method is distinguished by the if/switch statement, and in our case we will only handle calls called “getBatteryLevel”. The charge value is returned to the Flutter using the result.success(batteryLevel) call after the local method is retrieved.

MethodChannel – Flutter

Take a look at the code on the Flutter side first

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  static const platform = const MethodChannel('common.flutter/battery');

  void _incrementCounter() {
    setState(() {
      _counter++;
      _getBatteryLevel();
    });
  }

  @override
  Widget build(BuildContext context) {
    platform.setMethodCallHandler(platformCallHandler);
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
            Text('$_batteryLevel'),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), 
    );
  }

  String _batteryLevel = 'Unknown battery level.';

  Future<Null> _getBatteryLevel() async {
    String batteryLevel;
    try {
      final int result = await platform.invokeMethod('getBatteryLevel');
      batteryLevel = 'Battery level at $result % .';
    } on PlatformException catch (e) {
      batteryLevel = "Failed to get battery level: '${e.message}'.";
    }

    setState(() { _batteryLevel = batteryLevel; }); } // Future<dynamic> platformCallHandler(MethodCall call) async {switch (call.method) {case "get_message":
        return "Hello from Flutter";
        break; }}}Copy the code

First, define a constant result.success(platform), which is the same as the channel defined by the Android client. Next, define a result.success(_getBatteryLevel()) method to call the Android method, result.success(final int result = await platform.invokeMethod(‘getBatteryLevel’);) This line of code calls Native (Android) methods via channels. Because MethodChannel is called asynchronously, you must use the await keyword here.

In the Android code above we pass result.success(batteryLevel); Return to Flutter. Here the power is assigned directly to the result variable after execution of the await expression. Result.success (setState); To change the Text display value. Up to this point, native client methods are invoked through the Flutter side.

MethodChannel is a method that can be called both ways. In the code above, we have also shown that a native client calls a Flutter method.

In the primary side through methodChannel. InvokeMethod method call

methodChannel.invokeMethod("get_message", null, new MethodChannel.Result() {
                @Override
                public void success(@Nullable Object o) {
                    Log.d(TAG, "get_message:" + o.toString());
                }

                @Override
                public void error(String s, @Nullable String s1, @Nullable Object o) {

                }

                @Override
                public void notImplemented() {}});Copy the code

On the Flutter side you need to set a MethodCallHandler for the MethodChannel

static const platform = const MethodChannel('common.flutter/battery');
platform.setMethodCallHandler(platformCallHandler);
Future<dynamic> platformCallHandler(MethodCall call) async {
    switch (call.method) {
      case "get_message":
        return "Hello from Flutter";
        break; }}Copy the code

So that’s the relevant use of MethodChannel.

EventChannel

The data will be pushed to the Flutter, similar to the usual push function. If necessary, the Flutter will be pushed to the Flutter. It is up to the Flutter side to decide whether to process the push. In contrast to MethodChannel, which is actively fetched, EventChannel is passively pushed.

EventChannel native client

private String EVENT_CHANNEL = "common.flutter/message";
private int count = 0;
private Timer timer;

private void initEventChannel() {
        new EventChannel(getFlutterView(), EVENT_CHANNEL).setStreamHandler(new EventChannel.StreamHandler() {
            @Override
            public void onListen(Object arguments, EventChannel.EventSink events) {
                timer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        if (count < 10) {
                            count++;
                            events.success("Current time :" + System.currentTimeMillis());
                        } else{ timer.cancel(); }}}, 1000, 1000); } @Override public void onCancel(Object o) { } }); }Copy the code

In the code above, we create a timer that pushes a message to the Flutter every second, telling the Flutter the current time. In order to prevent continuous countdown, I made a count on my side and stopped sending after 10 times.

The EventChannel Flutter end

String message = "not message";
static const eventChannel = const EventChannel('common.flutter/message');
@override
  void initState() {
    super.initState();
    eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
  }

void _onEvent(Object event) {
    setState(() {
      message =
      "message: $event";
    });
  }

  void _onError(Object error) {
    setState(() {
      message = 'message: unknown.';
    });
  }
Copy the code

The code above shows that the Flutter side receives native client data by _onEvent and displays the data as Text. This implementation is relatively simple. In order to achieve business classification, data needs to be packaged into JSON, and corresponding business identifiers and data can be packaged through JSON data to distinguish.

BasicMessageChannel

BasicMessageChannel (mainly passing strings and some semi-structured data)

BasicMessageChannel Android end

private void initBasicMessageChannel() { BasicMessageChannel<Object> basicMessageChannel = new BasicMessageChannel<>(getFlutterView(), BASIC_CHANNEL, StandardMessageCodec.INSTANCE); Basicmessagechannel.send (basicMessagechannel.send ("send basic message", (object)-> {
                Log.e(TAG, "receive reply msg from flutter:"+ object.toString()); }); . / / receiving flutter message and send reply basicMessageChannel setMessageHandler ((object, reply) - > {the e (the TAG,"receive msg from flutter:" + object.toString());
            reply.reply("Reply: Got your message");

        });

    }
Copy the code

BasicMessageChannel Flutter end

  static const basicChannel = const BasicMessageChannel('common.flutter/basic', StandardMessageCodec()); Future<String> sendMessage() async {String reply = await basicChannel.send()'this is flutter');
    print("receive reply msg from native:$reply");
    returnreply; } / / receive the original message And send the reply to void receiveMessage () async {basicChannel. SetMessageHandler ((MSG) async {print("receive from Android:$msg");
      return "get native message";
    });
Copy the code

The codec used in the above example is StandardMessageCodec. In this example, the communication is always String, but StringCodec will also work.

Flutter provides three platforms and dart message communication modes.