Flutter hybrid development (Android) flutter communicates with Native

preface

As a hybrid development, it is inevitable for Flutter to interact with the native end. For example, calling the native system sensor and the network framework of the native end for data requests will use the methods of Flutter calling android and android calling Flutter. Here, Platform Channels are involved

Platform Channels

The Flutter transmits messages to the client through the Channel, as shown in the figure:

In the figure, the message transmission between the Flutter and the client is realized through the MethodChannel. MethodChannel is one of the Platform Channels, and Flutter has three communication types:

BasicMessageChannel: Used to pass string and semi-structured information

MethodChannel: Used to pass method calls( method invocation)Usually used to call native A method in

EventChannel: For data flow( event streams)Communication. It has a monitoring function, such as directly pushing data to the user after power changes flutter End.

In order to ensure the response of the UI, all messages delivered through Platform Channels are asynchronous.

For more information on the principle of channel, see this article: channel principle

Platform Channels use

1. Use of methodchannel

Native Client writing (take Android as an example)

First, define a method to obtain mobile phone power

private int getBatteryLevel() {
        return 90;
    }

This function is the method to be called for fluent. At this time, you need to establish this channel through MethodChannel.

First, add a method to initialize 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 void initMethodChannel() {
        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 int getBatteryLevel() {
        return 90;
    }

METHOD_CHANNEL is the identification used to interact with the flitter. Since there are usually multiple channels, it is necessary to maintain uniqueness in the app

Methodchannels are saved in the Map with the channel name as Key. Therefore, if two channels with the same name are set, only the later one will take effect.

onMethodCall has two parameters. onMethodCall contains the method name and parameters to be called. Result is the return value to fluent. The method name is set uniformly by the client and fluent. Judge the methodcall by the if/switch statement Method to distinguish different methods. In our example, we will only process the call named "getBatteryLevel". After calling the local method to obtain the power, use result The success (batterylevel) call returns the power value to the Flutter.

Methodchannel fluent end

Let's take a look at the code on the fluent 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;
    });
  }

 //Client call
  Future<dynamic> platformCallHandler(MethodCall call) async {
    switch (call.method) {
      case "get_message":
        return "Hello from Flutter";
        break;
    }
  }
}

The above code analysis: First, define a constant result Success (platform), which is consistent with the channel defined by Android client; Next, define a result The success (_getbatterylevel()) method is used to call the Android side method, result success(final int result = await platform.invokeMethod('getBatteryLevel');) This line of code calls the Native (Android) method through the channel. Because MethodChannel is called asynchronously, the await keyword must be used here.

In the above Android code, we get the power through result success(batteryLevel); Return to Flutter. Here, after the await expression is executed, the power is directly assigned to the result variable. Then through result success(setState); To change the Text display value. So far, the native client method is called through the fluent side.

MethodChannel is actually a method that can be called in both directions. In the above code, we also reflect the method of calling fluent through the native client.

On the native side, through methodchannel Method call of invokemethod

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() {

                }
            });

A MethodCallHandler needs to be set for the MethodChannel on the fluent side

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;
    }
  }

The above is the usage of MethodChannel.

EventChannel

Push the data to the shuttle end, similar to our common push function. Push it to the shuttle end if necessary. Whether to handle this push is up to the shuttle side. Compared with MethodChannel, it is active acquisition, while EventChannel is passive push.

EventChannel Native Client writing

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) {

            }
        });
    }

In the above code, we make a timer to push a message to Flutter every second to tell Flutter our current time. In order to prevent the countdown, I made a count and stopped sending more than 10 times.

Eventchannel fluent 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.';
    });
  }

The above code is to receive the native client data through the fluent end_ onEvent to receive data and display the data in Text. This implementation is relatively simple. If you want to achieve business classification, you need to encapsulate the data into json and distinguish it by wrapping some corresponding business identifiers and data in json data.

BasicMessageChannel

BasicMessageChannel (mainly used to transfer strings and some semi structured data)

BasicMessageChannel Android side

private void initBasicMessageChannel() {
        BasicMessageChannel<Object> basicMessageChannel = new BasicMessageChannel<>(getFlutterView(), BASIC_CHANNEL, StandardMessageCodec.INSTANCE);
        //Actively send messages to the shuttle and receive the reply of the shuttle message
            basicMessageChannel.send("send basic message", (object)-> {
                Log.e(TAG, "receive reply msg from flutter:" + object.toString());
            });

        //Receive the shuttle message and send a reply
        basicMessageChannel.setMessageHandler((object, reply)-> {
            Log.e(TAG, "receive msg from flutter:" + object.toString());
            reply.reply("reply: got your message");

        });

    }

Basicmessagechannel fluent end

  static const basicChannel = const BasicMessageChannel('common.flutter/basic', StandardMessageCodec());
//Send a message to the native client and receive a reply from the native client
  Future<String> sendMessage() async {
    String reply = await basicChannel.send('this is flutter');
    print("receive reply msg from native:$reply");
    return reply;
  }

  //Receive native messages and send replies
  void receiveMessage() async {
    basicChannel.setMessageHandler((msg) async {
      print("receive from Android:$msg");
      return "get native message";
    });

The codec used in the above example is StandardMessageCodec. In the example, the communication is String, and StringCodec can also be used.

The above is the three message communication modes of platform and dart provided by fluent.

Keywords: Flutter

Added by amsgwp on Mon, 10 Jan 2022 07:10:19 +0200