Flutter hybrid development: a detailed guide to communication between Android and flutter

This article starts with WeChat public's "Android development tour". Welcome to pay more attention to get more dry goods.

Communication scenario

When we do mixed development of Flutter, we usually need to communicate between Flutter and Native For example, Dart calls Native's album to select pictures, and Native actively transmits power and GPS information to Dart, etc. In hybrid development, there are usually the following types of communication:

  • Native passes data to Dart when initializing the fluent.
  • Native sends data to Dart.
  • Dart sends data to Native.
  • Dart sends data to Native, and then Native sends back data to dart.

The first way of communication we have explained when explaining the access of native project to Flutter. Interested students can move to Flutter hybrid development (I): detailed guide to integrating flutter module in Android project Look at it.

Communication mechanism

The communication mechanism between Flutter and Native end is completed through Platform Channel. The message is delivered at the Flutter end and the Native end using Channel. See the figure below for details:

It can be seen from the figure that the communication between the two ends is bidirectional and asynchronous. Flutter defines three different types of channels:

  • BasicMessageChannel: it is used to deliver string or semi-structured information, communicate continuously, and reply after receiving the information.
  • MethodChannel: used for passing method calls, one-time communication. It is usually used for Dart to call the Native method.
  • EventChannel: used for data flow communication, continuous communication, unable to reply this message after receiving the message. Usually used for Native to Dart communication.

Let's take a look at the specific use and introduction of these three Channel communication modes.

BasicMessageChannel

Android related methods:
BasicMessageChannel(BinaryMessenger messenger, String name, MessageCodec<T> codec)
  • The messenger parameter is a message Messenger (FlutterView), which is a tool for sending and receiving messages.
  • The name parameter is the name of the channel and its unique identification. It should be consistent with the Dart end.
  • Codec is a message codec, which also needs to be unified with dart. Its function is to encrypt the message when it is sent, and decrypt the message when it is received by dart end, all of which are binary data. It has four types: BinaryCodec, StringCodec, JSONMessageCodec, StandardMessageCodec, all of which belong to MessageCodec. If not specified, the default is StandardMessageCodec.

When we need to accept messages sent from Dart side, use setMessageHandler method:

void setMessageHandler(BasicMessageChannel.MessageHandler<T> handler)

The parameter handler is a message processor, which cooperates with BinaryMessenger to complete the processing of messages. It is an interface, which is implemented in onMessage method:

public interface MessageHandler<T> {
    void onMessage(T message, BasicMessageChannel.Reply<T> reply);
}

The parameter message is the data sent by Dart. Reply is a callback function used to reply messages. Reply ("") is provided to set the reply content.

The above is to accept the message of Dart end, and the send method is used to send the message of Native end. It has two overload methods:

void send(T message)
void send(T message, BasicMessageChannel.Reply<T> callback)

The parameter message is the data to be sent to Dart, and the callback callback is used to receive the reply message after Dart receives the message.

Dart end related methods:
const BasicMessageChannel(this.name, this.codec);

Here, the name is the same as the parameter of the construction method of codec and Android, so the name of the channel is also the unique identifier, codec is the message codec, and the two parameters must be unified at both ends.

If Dart side wants to accept the message of Native side, set setMessageHandler method:

void setMessageHandler(Future<T> handler(T message))

The parameter handler is a message processor, which cooperates with BinaryMessenger to complete the processing of messages.

Send a message to the Native side through the send method:

 Future<T> send(T message)

The parameter message is the parameter to be passed. Future < T > is a callback function waiting for Native to reply after sending a message.

BasicMessageChannel: Android and Flutter send messages to each other and return the information of the other party after receiving the message
Android Code:
 //Initialize BasicMessageChannel
BasicMessageChannel<String> basicMessageChannel = new BasicMessageChannel<>(flutterView,
                                "BasicMessageChannelPlugin",StringCodec.INSTANCE);
                
//Acceptance message
basicMessageChannel.setMessageHandler((message, reply) -> {
    mTvDart.setText(message);
    reply.reply("Received dart Data: accepted successfully");
});

//send message
basicMessageChannel.send(message, reply -> mTvDart.setText(reply));
Dart end code:
//Initialize BasicMessageChannel
static const BasicMessageChannel<String> _basicMessageChannel =
      BasicMessageChannel("BasicMessageChannelPlugin", StringCodec());

// Acceptance message
void handleBasicMessageChannel() {
    _basicMessageChannel
        .setMessageHandler((String message) => Future<String>(() {
              setState(() {
                showMessage = message;
              });
              return "Received Native Message from: accepted successfully";
            }));
  }
  
//send message
response = await _basicMessageChannel.send(_controller.text);
    setState(() {
      showMessage = response;
    });

The final effect is as follows: the part on the red split line is the Native page, and the lower part is the Flutter page.

MethodChannel

The parameter types and meanings of MethodChannel related methods are the same as those of BasicMessageChannel. We will not explain them one by one.

Androd end related methods:
MethodChannel(BinaryMessenger messenger, String name)
MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec)

The first constructor constructs a MethodCodec of type StandardMethodCodec.INSTANCE. MethodCodec defines two types: jsonmethodcode and StandardMethodCodec.

If you want to accept messages from Dart, use:

setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler) 

MethodCallHandler is the interface, and the callback method is:

public interface MethodCallHandler {
    void onMethodCall(MethodCall call, MethodChannel.Result result);
}

The call parameter has two member variables, the call.method of String type represents the name of the method to be called, and the call.arguments of Object type represents the input parameter passed by the method to be called. Result is the callback function that replies to this message and provides the result.success, result.error, result.notImplemented method call.

The invokeMethod method is used when the Dart code is called actively by sending a message

invokeMethod(@NonNull String method, @Nullable Object arguments)
invokeMethod(String method, @Nullable Object arguments, Result callback) 

The second method has a call back, which is used to accept the reply information of Dart after receiving the message.

public interface Result {

    void success(@Nullable Object result);

    void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails);

    void notImplemented();
}
Dart end related methods:
const MethodChannel(this.name, [this.codec = const StandardMethodCodec()])

By default, the constructor uses the StandardMethodCodec codec.

Accept the method call from Native through setMethodCallHandler method, and call the method on Native side through invokeMethod method.

 void setMethodCallHandler(Future<dynamic> handler(MethodCall call)) 
 Future<T> invokeMethod<T>(String method, [ dynamic arguments ])
MethodChannel actual combat: the Native end calls the getPlatform method of Dart end to return to the current os platform, and the Dart end calls the getBatteryLevel method of Native end to get the current phone power.
Android Code:
//Initialize MethodChannel
MethodChannel methodChannel = new MethodChannel(flutterView, "MethodChannelPlugin");

mBtnTitle.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //Call dart getPlatform method
        methodChannel.invokeMethod("getPlatform", null, new MethodChannel.Result() {
            @Override
            public void success(@Nullable Object result) {
                mTvDart.setText(result.toString());
            }

            @Override
            public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
                mTvDart.setText(errorCode + "==" + errorMessage);
            }

            @Override
            public void notImplemented() {
                mTvDart.setText("Unrealized getPlatform Method");
            }
        });
    }
});

//Accept the call from dart
methodChannel.setMethodCallHandler((call, result) -> {
    switch (call.method) {
        case "getBatteryLevel":
            int batteryLevel = getBatteryLevel();
            if (batteryLevel != -1) {
                result.success("The quantity of electricity is:" + batteryLevel);
            } else {
                result.error("1001", "Call error", null);
            }
            break;
        default:
            result.notImplemented();
            break;
    }
});
Dart end code:
// receive
void handleMethodChannelReceive() {
    Future<dynamic> platformCallHandler(MethodCall call) async {
      switch (call.method) {
        case "getPlatform":
          return getPlatformName(); //Call success method
    //          return PlatformException(code: '1002',message: 'exception occurred'); / / call error
          break;
      }
    }
    
    _methodChannel.setMethodCallHandler(platformCallHandler);
    //    _methodChannel.setMethodCallHandler(null); / / call notImplemented
}

//send
void handleMethodChannelSend() async {
    try {
      response = await _methodChannel.invokeMethod("getBatteryLevel");
      print(response);
      setState(() {
        showMessage = response;
      });
    } catch (e) {
      //Catch error and notImplemented exceptions
      setState(() {
        showMessage = e.message;
      });
    }
}
 

When we receive the native message using setMethodCallHandler, we can call the success callback on the native side by directly calling the relevant method.

If you throw an exception directly, such as PlatformException, you call the error callback on the Native side.

PlatformException(code: '1002',message: "Abnormal")

If we directly set handler to null

_methodChannel.setMethodCallHandler(null);

The notImplemented method callback on the Native side will be called.

Similarly, when we use the invokeMethod method on the Dart side, we need to catch exceptions so that we can receive the error and notImplemented method callbacks called by the Native side.

The final effect is as follows: the part on the red split line is the Native page, and the lower part is the Flutter page.

EventChannel

In fact, the internal implementation principle of EventChannel is completed through MethodChannel.

Android related code:
EventChannel(BinaryMessenger messenger, String name)
EventChannel(BinaryMessenger messenger, String name, MethodCodec codec)

Similarly, there are two constructs. The default codec is StandardMethodCodec. The codec of EventChannel and MethodChannel belong to MethodCodec.

Use setStreamHandler to listen for messages sent by Dart side,

void setStreamHandler(EventChannel.StreamHandler handler)

Where handler is an interface:

public interface StreamHandler {
    void onListen(Object args, EventChannel.EventSink eventSink);

    void onCancel(Object o);
}

args is the parameter for dart to initialize the listening stream. eventSink sets three callbacks, which are success, error and endofStream. Corresponding to ondata, error and onDone callbacks of dart side respectively.

Relevant codes of Dart end:
const EventChannel(this.name, [this.codec = const StandardMethodCodec()]);

Initialize a channel object through EventChannel. If receiving data from Native requires defining a broadcast stream:

Stream<dynamic> receiveBroadcastStream([ dynamic arguments ]) 

Register by calling the Stream's listen method.

Event channel actual battle: the Native end actively sends the power information to Dart end, and Dart end will display it after receiving the information.
Android Code:
EventChannel eventChannel = new EventChannel(flutterView, "EventChannelPlugin");
eventChannel.setStreamHandler(new EventChannel.StreamHandler() {
    @Override
    public void onListen(Object arguments, EventChannel.EventSink events) {
        events.success(arguments.toString() + getBatteryLevel());
        //events.error("111", "error occurred", "");
        //events.endOfStream();
    }

    @Override
    public void onCancel(Object arguments) {
    }
});
Dart end code:
//init
static const EventChannel _eventChannel = EventChannel("EventChannelPlugin");

//receive
void handleEventChannelReceive() {
streamSubscription = _eventChannel
    .receiveBroadcastStream() //Can carry parameters
    .listen(_onData, onError: _onError, onDone: _onDone);
}

void _onDone() {
setState(() {
  showMessage = "endOfStream";
});
}

_onError(error) {
setState(() {
  PlatformException platformException = error;
  showMessage = platformException.message;
});
}

void _onData(message) {
setState(() {
  showMessage = message;
});
}

Send the information through the event.success method, and the dart end obtains the information by listening to the Stream stream Stream. When event.error is called by the Native side, the error needs to be converted to PlatformException in the onError callback of dart side to get the information about the exception.

The final effect is as follows: the part on the red split line is the Native page, and the lower part is the Flutter page.

summary

It mainly explains the three communication modes of Android and Dart. Detailed analysis of the method structure and specific examples. Each method corresponds to different use scenarios. You can choose it according to your needs and practice more to make it skillful.

All the code snippets are posted. All the Demo source code has been uploaded to the backstage, and the public number is returned to the "hybrid development" to get the download link.

Recommended reading

Dart Foundation: dart quick start

Flutter hybrid development (I): detailed guide to integrating flutter module in Android project

Flutter hybrid development (II): detailed guide to integrated flutter module of iOS project

Scan below the two-dimensional code to pay attention to the public number and get more dry goods.

Keywords: Android codec iOS

Added by intech on Fri, 15 Nov 2019 10:26:28 +0200