This paper is mainly about the knowledge of Flutter and Native communication and interaction, mainly involving message transmission, Native View nesting and external texture. This paper is divided into the following points:

  1. Platform Channel
  2. PlatformView
  3. Texture

Flutter itself is only a UI framework, but a popular application not only has UI, but also has many other functions. Most of the non-UI capabilities are realized by Native, such as Bluetooth, camera, GPS, etc. Flutter here is more a carrier to display non-UI functions.

Platform Channel

The first one is the Platform Channel of Flutter, also known as the Platform Channel. Its main function is message transmission between Flutter and Native. It realizes the communication between Flutter and Native, and establishes the communication bridge between Flutter and Native. Many plug-ins are based on it. Platform Channel supports Objective-C and Swift on iOS and Java and Kotlin on Android.

The basic communication modes between Platform Channel and Native are as follows:

  1. The Flutter sends messages to the Native end through the Platform Channel. The Native end receives, extracts parameters, and returns the result
  2. The Native terminal sends parameters to the Flutter terminal through the Platform Channel

Flutter provides three channels for developers to use. Each Channel has its own characteristics and disadvantages. The Method Channel is commonly used. The other two are BasicMessage Channel and Event Channel:

  • Method Channel: Realizes the two-way communication between Flutter and Native, mainly by calling methods to each other
  • BasicMessage Channel: The bidirectional communication between Flutter and Native is realized by transferring some semi-structured data and strings to each other
  • EventChannel: only Native sends messages to the Flutter, which is one-way

Method Channel

Method Channel is the most commonly used Platform Channel, and its Codec is MethodCodec. On the Flutter end, Method Channel calls Native methods asynchronously. At the Native end, after receiving the message, it conducts business processing by filtering the method name and parameters of the other side and returns data as required. This process occurs in the main thread:

The Method Channel can deliver messages that are not arbitrary, but specify a limited number of data types, and use standard codecs to encode and decode messages to efficiently implement binary serialization and deserialization of messages. The following is the mapping between Dart data types and Native data types:

Dart iOS Android
null nil null
bool NSNumber numberWithBool: java.lang.Boolean
int NSNumber numberWithInt: java.lang.Integer
Int, if less than 32 bits NSNumber numberWithLong: java.lang.Long
Int, if less than 64 bits FlutterStandardBigInteger java.math.BigInteger
double NSNumber numberWithDouble: java.lang.Double
String NSString java.lang.String
Uint8List FlutterStandardTypedData typedDataWithBytes: byte[]
Int32List FlutterStandardTypedData typedDataWithInt32: int[]
Int64List FlutterStandardTypedData typedDataWithInt64: long[]
Float64List FlutterStandardTypedData typedDataWithFloat64: double[]
List NSArray java.util.ArrayList
Map NSDictionary java.util.HashMap

When values are sent and received, the serialization and deserialization of those values in the message takes place automatically.

use

The Flutter end:

// Initialize MethodChannel
const platform = MethodChannel('channelName');

@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Container(
        alignment: Alignment.center,
        child: TextButton(
          onPressed: () {
            // Call Native methods
            platform.invokeMethod('openAppStore');
          },
          child: Text('Crack the app market'),),),); }Copy the code

MethodChannel is also relatively simple to use. When initializing a MethodChannel instance, the channel name must be unique in both Native and Flutter.

MethodChannel provides three ways to pass messages to Native, with the difference being the return value type:

  • Future

    invokeMethod(method, [arguments]) : you must pass the method name, optional arguments, and return single data
    ?>
  • Future invokeListMethod(method, [arguments]) returns an array ?>
  • Future
    ? > invokeMapMethod(method, [arguments]) : The argument list functions as the first one, and returns a Map

The iOS side:

let flutterViewController = self.window.rootViewController as! FlutterViewController
/ / registered Channel
let channel = FlutterMethodChannel(name: "channelName", binaryMessenger:flutterViewController.binaryMessenger)

// Handles callbacks, the MethodHandler used by MethodChannel
channel.setMethodCallHandler { call, result in
    if call.method == "openAppStore" {
        UIApplication.shared.openURL(URL(string: "itms-apps://itunes.apple.com/xy/app/foo/id414478124")! }}Copy the code

This explains how a Flutter sends messages to its Native endpoint. Conversely, a Flutter sends messages to its Native endpoint by calling a similar invokeMethod method. The Flutter sets up a Handler to process the messages and return them.

BasicMessage Channel allows Flutter and Native to communicate with each other based on message passing. The MessageCodec of BasicMessage Channel is MessageCodec, so different message codecs can be flexibly set. In addition, message codecs can also be customized to meet business requirements.

Message Codec Codec

The MessageCodec included with Flutter currently includes:

  • BinaryCodec
  • StringCodec
  • JSONMessageCodec
  • StandardCodec

use

The Flutter end:

/ / initialization
const basicMessagePlatform = BasicMessageChannel(
  'basicMessageChannelName',
  StringCodec(),
);

// Send a message
_sendMessage(String arguments) {
  basicMessagePlatform.send(arguments);
}

// Receive the message

_receiverMessage() {
  basicMessagePlatform.setMessageHandler((message) async {
    print(message);
    return null;
  });
}
Copy the code

The BasicMessageChannel initialization needs to ensure that the name of the Flutter and Native are unique. It also needs to determine the type of the codec. In this case, StringCodec is used.

BasicMessageChannel is not based on method calls, but on message sending, which is delivered to Native via the Send method. MessageHandler can also be registered to receive and process messages.

The iOS side:

let basicMessageChannel = FlutterBasicMessageChannel(name: "basicMessageChannelName", binaryMessenger: flutterViewController.binaryMessenger, codec: FlutterStringCodec ()) / / receiving messages basicMessageChannel. SetMessageHandler {message, Reply in print (the message)} processing messages basicMessageChannel. SendMessage (" Hello Flutter ")Copy the code

Event Channel

Event Channel is a Channel used for one-way transmission, and the Native sends messages to Flutter actively.

The Flutter end:

const eventChannel = EventChannel('EventChannelName');
// Receive Native messages and process them
eventChannel.receiveBroadcastStream().listen((event) {
  print(event);
});
Copy the code

The iOS side:

Private var eventSink: FlutterEventSink? Let eventChannel = FlutterEventChannel(name: "EventChannelName", binaryMessenger: FlutterViewController binaryMessenger) / / set the data to monitor, used to monitor whether the Flutter to start listening to the Event Channel, Incoming instance FlutterStreamHandler agreement to follow the eventChannel. SetStreamHandler (self); // Flutter will no longer receive func onCancel(withArguments arguments: Any?). -> FlutterError? {return nil} // The Flutter end will call back when it starts listening for this Channel. The second argument, EventSink, is the carrier to pass data to. func onListen(withArguments arguments: Any? , eventSink events: @escaping FlutterEventSink) -> FlutterError? {self.eventSink = events return nil}} self.eventSink? ("hello")Copy the code

PlatformView

Platform Channel can well solve the problem of data transfer between the Flutter and Native terminals. However, in practical development, it is sometimes impossible to completely leave the Native components, such as using WebView to display protocols or using system controls to view maps. Although you can open a WebView via Platform Channel, you cannot combine the WebView with the Flutter UI.

To solve this problem, Flutter 1.0 provides an AndroidView and UIKitView component to embed Android and iOS native components into the Widget component tree of Flutter, enabling Flutter to have the same functionality as native components. It doesn’t cost much to re-implement the functionality.

AndroidView and UIKitView are collectively called PlatformView, and we’ll see more and more of them as we iterate.

The iOS side configuration

Here I’ll use the example of showing the iOS UILable control.

Set IO. Flutter. Embedded_views_preview to true:

<key>io.flutter.embedded_views_preview</key>
<true/>
Copy the code

Second, create a class PlatformTextView that inherits NSobject and implements FlutterPlatform’s init(_,viewID:args) and view() methods:

class PlatformTextView: NSObject, FlutterPlatformView { let frame: CGRect; let viewId: Int64; Var text:String = "" // Init (_ frame: CGRect,viewID: Int64, args :Any?) { self.frame = frame self.viewId = viewID if(args is NSDictionary){ let dict = args as! NSDictionary self.text = dict.value(forKey: "text") as! Func View () -> UIView {let label = UILabel() label.text = self.text label.textColor = UIColor.red label.frame = self.frame return label } }Copy the code

Step three, create the PlatformTextViewFactory class, Create FlutterPlatformViewFactory agreement (withFrame: viewIdentifier: the arguments:) and createArgsCodec () method:

// Call func create(withFrame Frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) when a Flutter initializes UIKit components. -> FlutterPlatformView { return PlatformTextView(frame,viewID: viewId,args: Func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {return FlutterStandardMessageCodec.sharedInstance() }Copy the code

Step 4: Register Plugin and viewID:

let facetory = PlatformTextViewFactory() let register = self.registrar(forPlugin: "platform_text_view_plugin") register? .register(facetory, withId: "platform_text_view")Copy the code

The Flutter end

The use of the Flutter side of the Native component is similar to that of any other Widget, by directly initializing and passing parameters where appropriate:

@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: _getPlatformTextView(),, ); } _getPlatformTextView() { if (defaultTargetPlatform == TargetPlatform.android) { return AndroidView( viewType: 'platform_text_view', creationParams: <String, dynamic>{'text': 'Android Text View'}, creationParamsCodec: StandardMessageCodec(), ); } else { return UiKitView( viewType: 'platform_text_view', creationParams: <String, dynamic>{'text': 'ios Text View'}, creationParamsCodec: StandardMessageCodec(), ); }}Copy the code

Texture

PlatformView can use Native components to implement existing functions of the system, but this will also cause the project to be less “Flutter”, that is, to rely on Native components. If sometimes we only need some Native data, directly attaching a View will be a little cumbersome.

This may be associated with using Platform Channel to transfer data from Native to Flutter. It is acceptable to use Platform Channel to transfer data from Native to Flutter in a few cases. However, if there is a video stream with 30 frames per second, To solve this problem, Flutter provides a Wiget based on the shared image data between Flutter and Native.

const Texture({
  Key key,
  @required this.textureId,
})
Copy the code

The Texture component is associated with the Texture data via textureId. Those who have done OpenGL can understand the Texture and Texture Id relationship, which is a bit like the relationship between Pointers and Pointers to memory. The Texture component draws the Texture data with the corresponding ID from memory and then draws it. The TextureId used by the Texture component can be passed using the Platform Channel:

_textureId = await channel.platform.invokeMethod('create', args);
Copy the code

conclusion

The knowledge of Flutter and Native communication and interaction will be briefly discussed here, mainly focusing on the characteristics and use of Platform Channel, PlatformView and Texture. For some simple data transfer, the Platform Channel is used directly. To use Native components, you can either call them externally using Platform Channel or embed them in the Flutter Widget tree using PlatformView. For image data, you can use Texture to overlay the Texture to ensure the purity of the Flutter and also meet your needs.