preface

Practical development usually requires that the Flutter invoke the Native function, or the Native invoke the Flutter function

The communication between them is mainly achieved through Platform Channel, and there are three main channels:

  • MethodChannel is used to pass method calls
  • EventChannel is used for the communication of Event Streams
  • BasicMessageChannel is used to pass strings and semi-structured information

The following figure shows message passing between Flutter and Native using MethodChannel as an example:

To ensure the smoothness of the application and timely response to user operations, message and response transfer between Flutter and Native is asynchronous, but the channel API needs to be called in the main thread

Data types supported by Platform Channel

Platform Channel uses standard message codecs to automatically serialize and deserialize data for us as we send and receive it

The codec supports the following data types:

Dart Android iOS
null null nil (NSNull when nested)
bool java.lang.Boolean NSNumber numberWithBool:
int java.lang.Integer NSNumber numberWithInt:
int(if 32 bits not enough) java.lang.Long NSNumber numberWithLong:
double java.lang.Double NSNumber numberWithDouble:
String java.lang.String NSString
Uint8List byte[] FlutterStandardTypedData typedDataWithBytes:
Int32List int[] FlutterStandardTypedData typedDataWithInt32:
Int64List long[] FlutterStandardTypedData typedDataWithInt64:
Float64List double[] FlutterStandardTypedData typedDataWithFloat64:
List java.util.ArrayList NSArray
Map java.util.HashMap NSDictionary

MethodChannel

Take Flutter to obtain mobile battery power as an example. In order to obtain Android/iOS battery power in Flutter interface, the function of obtaining battery power should be written in Native to be called by Flutter

Native client code

public class MainActivity extends FlutterActivity {
  private static final String CHANNEL = "com.example.flutter_battery/battery"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); GeneratedPluginRegistrant.registerWith(this); New MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler((call, result) -> {// executes in the main threadif (call.method.equals("getBatteryLevel"Int batteryLevel = fetchBatteryLevel();if(batteryLevel ! Result. success(batteryLevel); }else {
                  result.error("UNAVAILABLE"."Battery level not available.", null); }}else{ result.notImplemented(); }}); } // Get the power private intfetchBatteryLevel() {
    int batteryLevel;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE);
      batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
    } else {
      Intent intent = new ContextWrapper(getApplicationContext()).
              registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
      batteryLevel = (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100) /
              intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
    }

    returnbatteryLevel; }}Copy the code

In Native code, we create a new fetchBatteryLevel function to get the battery, and then a new MethodChannel object

Note here that the constructor’s second argument, CHANNEL, is also a string that will be used later in Flutter

Finally, we set up the MethodChannel function to call the handler MethodCallHandler. This MethodCallHandler is called when the Flutter calls the Native function

Flutter server-side code

Class _MyHomePageState extends State<MyHomePage> {// The constructor argument is the Android CHANNEL constant static const methodChannelBattery = const MethodChannel('com.example.flutter_battery/battery');

  String _batteryLevel = 'Unknown battery level.';

  Future<void> _getBatteryLevel() async {
    String batteryLevel;
    try {
      // invokeMethod('getBatteryLevel') will the callback MethodCallHandler final int result = await methodChannelBattery. InvokeMethod ('getBatteryLevel');
      batteryLevel = 'Battery level at $result % .';
    } on PlatformException catch (e) {
      batteryLevel = "Failed to get battery level: '${e.message}'.";
    } on MissingPluginException catch (e) {
      batteryLevel = "plugin undefined";
    }
    setState(() {
      _batteryLevel = batteryLevel;
    });
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Container(
        margin: EdgeInsets.only(left: 10, top: 10),
        child: Center(
          child: Column(
            children: [
              Row(
                children: <Widget>[
                  RaisedButton(
                    child: Text(
                      'GetBatteryFromNative', style: TextStyle(fontSize: 12), ), onPressed: _getBatteryLevel, ), Padding( padding: EdgeInsets.only(left: 10), child: Text(_batteryLevel), ) ], ), ], ), ), ), ); }}Copy the code

Click the button on the Flutter screen to get the battery on your Android phone:

MethodChannel can also call Native Flutter functions in addition to implementing a Flutter

Call the invokeMethod method on the Native side to specify which Flutter method you want to invoke:

@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
    if (call.method.equals("getBatteryLevel")) {
        int batteryLevel = fetchBatteryLevel();

        if(batteryLevel ! = -1) { result.success(batteryLevel); }else {
            result.error("UNAVAILABLE"."Battery level not available.", null); }}else{ result.notImplemented(); } // Native calls Flutter getFlutterContent channel.invokemethod ("getFlutterContent", null, new MethodChannel.Result() {
        @Override
        public void success(Object o) {
            Log.e("BatteryPlugin"."Dart getFlutterContent() result : " + o);
        }

        @Override
        public void error(String s, String s1, Object o) {
            Log.e("BatteryPlugin"."Dart getFlutterContent() error : " + s);
        }

        @Override
        public void notImplemented() {
            Log.e("BatteryPlugin"."Dart getFlutterContent() notImplemented"); }}); }Copy the code

A MethodCallHandler for a MethodChannel is set up on a Flutter. The Native method calls the invokeMethod method.

void initState() {
    super.initState();
    methodChannelBattery.setMethodCallHandler(batteryCallHandler);
}

Future<dynamic> batteryCallHandler(MethodCall call) async {
    switch (call.method) {
      case "getFlutterContent":
        return "This is FlutterContent"; }}Copy the code

The main meaning of the above code is that when we click the button to call the function in Native to get the power, then immediately call the getFlutterContent function in the Flutter in Native

The console will then output the return value from Flutter getFlutterContent() :

Dart getFlutterContent() result : This is FlutterContent
Copy the code

EventChannel

EventChannel is suitable for event stream communication. For example, Native needs to send frequent messages to Flutter, such as monitoring network status, Bluetooth devices, and so on, and then send them to Flutter

Here is an example of the use of EventChannel that sends one event per second to a Flutter in Native:

Native client code

public class EventChannelPlugin implements EventChannel.StreamHandler {

    private Handler handler;
    private static final String CHANNEL = "com.example.flutter_battery/stream"; private int count = 0; Public static void registerWith(pluginregistry.registrar) { The CHANNEL constant works the same as MethodChannel final EventChannel CHANNEL = new EventChannel(Registr.messenger (), CHANNEL); // Set the stream's handler (StreamHandler) channel.setStreamHandler(new EventChannelPlugin()); } @Override public void onListen(Object o, Eventchannel.eventsink EventSink) {// Every second number +1 handler = new handler (message -> {// Then send the number to Flutter eventSink.success(++count); handler.sendEmptyMessageDelayed(0, 1000);return false; }); handler.sendEmptyMessage(0); } @Override public void onCancel(Object o) { handler.removeMessages(0); handler = null; count = 0; }}Copy the code

Flutter server-side code

Class _MyHomePageState extends State<MyHomePage> {static const stream = const EventChannel('com.example.flutter_battery/stream');

  int _count = 0;

  StreamSubscription _timerSubscription;

  void _startTimer() {
    if(_timerSubscription == null) Will trigger the Native onListen callback _timerSubscription = stream. ReceiveBroadcastStream () listen (_updateTimer); } void_stopTimer() { _timerSubscription? .cancel(); _timerSubscription = null;setState(() => _count = 0);
  }

  void _updateTimer(dynamic count) {
    print("-- -- -- -- -- -- -- --$count");
    setState(() => _count = count);
  }

  @override
  void dispose() { super.dispose(); _timerSubscription? .cancel(); _timerSubscription = null; } @override Widget build(BuildContext context) {return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Container(
        margin: EdgeInsets.only(left: 10, top: 10),
        child: Center(
          child: Column(
            children: [
              Row(
                children: <Widget>[
                  RaisedButton(
                    child: Text('Start EventChannel',
                        style: TextStyle(fontSize: 12)),
                    onPressed: _startTimer,
                  ),
                  Padding(
                      padding: EdgeInsets.only(left: 10),
                      child: RaisedButton(
                        child: Text('Cancel EventChannel',
                            style: TextStyle(fontSize: 12)),
                        onPressed: _stopTimer,
                      )),
                  Padding(
                    padding: EdgeInsets.only(left: 10),
                    child: Text("$_count"() [() [() [() [() [() }}Copy the code

The effect is shown below:

BasicMessageChannel

BasicMessageChannel is more like communication of a message and can be used for simple communication rather than calling a method or event flow

BasicMessageChannel can also implement bidirectional communication between Flutter and Native. The following illustration is an official example:

Native FAB
Flutter
Flutter FAB
Native

Flutter server-side code

class _MyHomePageState extends State<MyHomePage> {
  static const String _channel = 'increment';
  static const String _pong = 'pong';
  static const String _emptyMessage = ' ';
  static const BasicMessageChannel<String> platform =
      BasicMessageChannel<String>(_channel, StringCodec());

  int _counter = 0;

  @override
  void initState() { super.initState(); / / set the message processor platform. SetMessageHandler (_handlePlatformIncrement); } // If a Native message is received, the number +1 Future<String> _handlePlatformIncrement(String message) async {setState(() { _counter++; }); // Send an empty messagereturn_emptyMessage; } // Click FAB in Flutter to send messages to Native void_sendFlutterIncrement() {
    platform.send(_pong);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('BasicMessageChannel'),
      ),
      body: Container(
          child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Expanded(
            child: Center(
              child: Text(
                  'Platform button tapped $_counter time${_counter == 1 ? '':'s'}., style: const TextStyle(fontSize: 17.0),),), Container(padding: const EdgeInsets. Only (bottom: 15.0, left: 5.0), child: Row(children: <Widget>[image.asset ('assets/flutter-mark-square-64.png', scale: 1.5),
                const Text('Flutter', style: TextStyle(fontSize: 30.0)),],),),],)), floatingActionButton: floatingActionButton (onPressed: _sendFlutterIncrement, child: const Icon(Icons.add), ), ); }}Copy the code

Native client code

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // omit other code... messageChannel = new BasicMessageChannel<>(flutterView, CHANNEL, StringCodec.INSTANCE); messageChannel.setMessageHandler(new MessageHandler<String>() { @Override public void onMessage(String s, Reply<String> Reply) {// Receive a Flutter message and update Native onFlutterIncrement(); reply.reply(EMPTY_MESSAGE); }}); FloatingActionButton fab = findViewById(R.id.button); fab.setOnClickListener(new View.OnClickListener() {@override public void onClick(View v) {// Notify the Flutter to update sendAndroidIncrement(); }}); } private voidsendAndroidIncrement() {
    messageChannel.send(PING);
}

private void onFlutterIncrement() {
    counter++;
    TextView textView = findViewById(R.id.button_tap);
    String value = "Flutter button tapped " + counter + (counter == 1 ? " time" : " times");
    textView.setText(value);
}
Copy the code

So much for the communication between Flutter and Native. In summary, you can use MethodChannel if communicating requires method calls, EventChannel if communicating with data flows, and BasicMessageChannel if communicating with just message notifications.

Reference

Flutter. Dev/docs/develo… Juejin. Cn/post / 684490… Juejin. Cn/post / 684490… Juejin. Cn/post / 684490…

To contact me

The following is my public number, dry goods articles not bad, there is a need to pay attention to, there are any questions can contact me: