The Flutter hybrid development family includes the following:

  • Embed native View-Android
  • Embed native View-ios
  • Communicate with native -MethodChannel
  • Communicate with native -BasicMessageChannel
  • Communicate with native -EventChannel
  • Add Flutter to Android Activity
  • Add Flutter to Android Fragment
  • Add Flutter to iOS

Share one article every weekday, welcome to follow, like and forward.

iOS View

It is recommended to use Xcode for development. Under the left Project TAB of Android Studio, select any file in the ios directory, and Open ios Module in Xcode will appear in the upper right corner.

Click to open it, as follows:

Create an iOS View in the Runner directory that inherits the FlutterPlatformView and returns a simple UILabel:

import Foundation
import Flutter

class MyFlutterView: NSObject.FlutterPlatformView { let label = UILabel() init(_ frame: CGRect,viewID: Int64,args :Any? ,messenger :FlutterBinaryMessenger) { label.text ="I'm iOS View"
    }
    
    func view() -> UIView {
        return label
    }   
}
Copy the code
  • GetView: Returns the iOS View

Registered PlatformView

Create MyFlutterViewFactory:

import Foundation
import Flutter

class MyFlutterViewFactory: NSObject.FlutterPlatformViewFactory {
    
    var messenger:FlutterBinaryMessenger
    
    init(messenger:FlutterBinaryMessenger) {
        self.messenger = messenger
        super.init()
    }
    
    func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView {
        return MyFlutterView(frame,viewID: viewId,args: args,messenger: messenger)
    }
    
    func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
        return FlutterStandardMessageCodec.sharedInstance()
    }
}

Copy the code

Register in the AppDelegate:

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
    
    let registrar:FlutterPluginRegistrar = self.registrar(forPlugin: "plugins.flutter.io/custom_platform_view_plugin")!
    let factory = MyFlutterViewFactory(messenger: registrar.messenger())
    registrar.register(factory, withId: "plugins.flutter.io/custom_platform_view")
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

Copy the code

Remember the plugins. Flutter. IO/custom_platform_view, this string is needed in the flutter and consistent.

Embedded Flutter

Called in Flutter

class PlatformViewDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Widget platformView() {
      if (defaultTargetPlatform == TargetPlatform.android) {
        return AndroidView(
          viewType: 'plugins.flutter.io/custom_platform_view',
          onPlatformViewCreated: (viewId) {
            print('viewId:$viewId');
            platforms
                .add(MethodChannel('com.flutter.guide.MyFlutterView_$viewId'));
          },
          creationParams: {'text': 'Parameters that a Flutter passes to AndroidTextView'},
          creationParamsCodec: StandardMessageCodec(),
        );
      }else if(defaultTargetPlatform == TargetPlatform.iOS){
        return UiKitView(
          viewType: 'plugins.flutter.io/custom_platform_view',); }}returnScaffold( appBar: AppBar(), body: Center( child: platformView(), ), ); }}Copy the code

DefaultTargetPlatform == targetPlatform. iOS

Set initialization parameters

The Flutter end is modified as follows:

UiKitView(
          viewType: 'plugins.flutter.io/custom_platform_view',
          creationParams: {'text': 'Parameters that a Flutter passes to IOSTextView'},
          creationParamsCodec: StandardMessageCodec(),
        )
Copy the code
  • CreationParams: Parameter passed by the plugin to the constructor of AndroidView.
  • creationParamsCodec: creationParams is encoded and sent to the platform side, which should match the codec passed to the constructor. Range of values:
    • StandardMessageCodec
    • JSONMessageCodec
    • StringCodec
    • BinaryCodec

Modify MyFlutterView:

import Foundation
import Flutter

class MyFlutterView: NSObject.FlutterPlatformView { let label = UILabel() init(_ frame: CGRect,viewID: Int64,args :Any? ,messenger :FlutterBinaryMessenger) {super.init()
        if(args is NSDictionary){
            let dict = args as! NSDictionary
            label.text  = dict.value(forKey: "text") as! String
        }
    }
    
    func view() -> UIView {
        return label
    }
    
}

Copy the code

End result:

The Flutter sends messages to the iOS View

Modify the Flutter end to create MethodChannel for communication:

class PlatformViewDemo extends StatefulWidget {
  @override
  _PlatformViewDemoState createState() => _PlatformViewDemoState();
}

class _PlatformViewDemoState extends State<PlatformViewDemo> {
  static const platform =
      const MethodChannel('com.flutter.guide.MyFlutterView');

  @override
  Widget build(BuildContext context) {
		Widget platformView() {
      if (defaultTargetPlatform == TargetPlatform.android) {
        return AndroidView(
          viewType: 'plugins.flutter.io/custom_platform_view',
          creationParams: {'text': 'Parameters that a Flutter passes to AndroidTextView'},
          creationParamsCodec: StandardMessageCodec(),
        );
      } else if (defaultTargetPlatform == TargetPlatform.iOS) {
        return UiKitView(
          viewType: 'plugins.flutter.io/custom_platform_view',
          creationParams: {'text': 'Parameters that a Flutter passes to IOSTextView'}, creationParamsCodec: StandardMessageCodec(), ); }}return Scaffold(
      appBar: AppBar(),
      body: Column(children: [
        RaisedButton(
          child: Text('Pass parameters to native View'),
          onPressed: () {
            platform.invokeMethod('setText', {'name': 'laomeng'.'age': 18}); }, ), Expanded(child: platformView()), ]), ); }}Copy the code

Also create a MethodChannel in the native View for communication:

import Foundation
import Flutter

class MyFlutterView: NSObject.FlutterPlatformView { let label = UILabel() init(_ frame: CGRect,viewID: Int64,args :Any? ,messenger :FlutterBinaryMessenger) {super.init()
        if(args is NSDictionary){
            let dict = args as! NSDictionary
            label.text  = dict.value(forKey: "text") as! String
        }
        
        let methodChannel = FlutterMethodChannel(name: "com.flutter.guide.MyFlutterView", binaryMessenger: messenger)
        methodChannel.setMethodCallHandler { (call, result) in
            if (call.method == "setText") {
                if let dict = call.arguments as? Dictionary<String, Any> {
                    let name:String = dict["name"] as? String ?? ""
                    let age:Int = dict["age"] as? Int ?? - 1
                    self.label.text = \(name), Age: \(age)"
                }
            }
        }
    }
    
    func view() -> UIView {
        return label
    }
    
}

Copy the code

Flutter fetches messages to the Android View

Instead of sending messages, a Flutter requests data to the native, which returns data to the Flutter end, modifying MyFlutterView onMethodCall:

import Foundation
import Flutter

class MyFlutterView: NSObject.FlutterPlatformView { let label = UILabel() init(_ frame: CGRect,viewID: Int64,args :Any? ,messenger :FlutterBinaryMessenger) {super.init()
        if(args is NSDictionary){
            let dict = args as! NSDictionary
            label.text  = dict.value(forKey: "text") as! String
        }
        
        let methodChannel = FlutterMethodChannel(name: "com.flutter.guide.MyFlutterView", binaryMessenger: messenger)
        methodChannel.setMethodCallHandler { (call, result:FlutterResult) in
            if (call.method == "setText") {
                if let dict = call.arguments as? Dictionary<String, Any> {
                    let name:String = dict["name"] as? String ?? ""
                    let age:Int = dict["age"] as? Int ?? - 1
                    self.label.text = \(name), Age: \(age)"}}else if (call.method == "getData") {
                if let dict = call.arguments as? Dictionary<String, Any> {
                    let name:String = dict["name"] as? String ?? ""
                    let age:Int = dict["age"] as? Int ?? - 1
                    result(["name":name,"age":age])
                }
            }
        }
    }
    
    func view() -> UIView {
        return label
    }
    
}

Copy the code

Result () is the data returned.

Data received by the Flutter end:

var _data = 'Get data';

RaisedButton(
  child: Text('$_data'),
  onPressed: () async {
    var result = await platform
        .invokeMethod('getData', {'name': 'laomeng'.'age': 18});
    setState(() {
      _data = '${result['name']}.${result['age']}'; }); },),Copy the code

Resolve multiple native View communication conflicts

Of course the page has three native views,

class PlatformViewDemo extends StatefulWidget {
  @override
  _PlatformViewDemoState createState() => _PlatformViewDemoState();
}

class _PlatformViewDemoState extends State<PlatformViewDemo> {
  static const platform =
      const MethodChannel('com.flutter.guide.MyFlutterView');

  var _data = 'Get data';

  @override
  Widget build(BuildContext context) {
		Widget platformView() {
      if (defaultTargetPlatform == TargetPlatform.android) {
        return AndroidView(
          viewType: 'plugins.flutter.io/custom_platform_view',
          creationParams: {'text': 'Parameters that a Flutter passes to AndroidTextView'},
          creationParamsCodec: StandardMessageCodec(),
        );
      } else if (defaultTargetPlatform == TargetPlatform.iOS) {
        return UiKitView(
          viewType: 'plugins.flutter.io/custom_platform_view',
          creationParams: {'text': 'Parameters that a Flutter passes to IOSTextView'}, creationParamsCodec: StandardMessageCodec(), ); }}return Scaffold(
      appBar: AppBar(),
      body: Column(children: [
        Row(
          children: [
            RaisedButton(
              child: Text('Pass parameters to native View'),
              onPressed: () {
                platform
                    .invokeMethod('setText', {'name': 'laomeng'.'age': 18});
              },
            ),
            RaisedButton(
              child: Text('$_data'),
              onPressed: () async {
                var result = await platform
                    .invokeMethod('getData', {'name': 'laomeng'.'age': 18});
                setState(() {
                  _data = '${result['name']}.${result['age']}'; }); }, ), ], ), Expanded(child: Container(color: Colors.red, child: platformView())), Expanded(child: Container(color: Colors.blue, child: platformView())), Expanded(child: Container(color: Colors.yellow, child: platformView())), ]), ); }}Copy the code

Now click the Pass parameter to the native View button which View will change the content, actually only the last one will change.

How do I change the contents of a specified View? The key is MethodChannel, just change the name of the above three channels to be different:

  • The first method: Pass a unique ID through an initialization parameter to the native View, which uses this ID to build a MethodChannel with a different name.
  • The second method (recommended) : when a native View is generated, the system generates a unique ID for it: viewId, which is used to build a MethodChannel with a different name.

Native Views use viewId to build methodChannels with different names:

import Foundation
import Flutter

class MyFlutterView: NSObject.FlutterPlatformView { let label = UILabel() init(_ frame: CGRect,viewID: Int64,args :Any? ,messenger :FlutterBinaryMessenger) {super.init()
        if(args is NSDictionary){
            let dict = args as! NSDictionary
            label.text  = dict.value(forKey: "text") as! String
        }
        
        let methodChannel = FlutterMethodChannel(name: "com.flutter.guide.MyFlutterView_\(viewID)", binaryMessenger: messenger)
        methodChannel.setMethodCallHandler { (call, result:FlutterResult) in. } } func view() -> UIView {return label
    }
    
}

Copy the code

The Flutter side creates a different MethodChannel for each native View:

var platforms = [];

UiKitView(
  viewType: 'plugins.flutter.io/custom_platform_view',
  onPlatformViewCreated: (viewId) {
    print('viewId:$viewId');
    platforms
        .add(MethodChannel('com.flutter.guide.MyFlutterView_$viewId'));
  },
  creationParams: {'text': 'Parameters that a Flutter passes to AndroidTextView'},
  creationParamsCodec: StandardMessageCodec(),
)
Copy the code

To send a message to the first:

platforms[0]
    .invokeMethod('setText', {'name': 'laomeng'.'age': 18});
Copy the code