I’ve been learning about Flutter for a while now, so here’s how Flutter interacts with the native.

Needless to say, the importance of native interaction. Flutter, after all, is not everything. Sometimes it needs our native support to achieve all kinds of strange needs. So just start Flutter without saying a word.

1. Create a Flutter project

This time our goal is to interact with the native, so the creation method will be different from the previous one. The previous one was the Normal project of the Flutter Application. This time we will choose the Flutter Module interaction project

As can be seen from the above image, the newly created project has a. In front of the name of the android and ios folders, unlike the normal Flutter Application. You can see that these two folders are hidden folders.

So why hide these two folders? A: The contents of these folders are the same as the normal Flutter Application, but these two projects are only for testing and do not participate in native interaction. The test is when we Run a project in Android Studio.

2. Create a Native project in Xcode

Create an Xcode project with the same path as the Flutter project as shown below

3. Add dependencies

3.1 Cocoapods introduces Flutter support
  1. $CD Native project path
  2. $pod init
  3. Edit podFile contents(Content below, pay attention to the Chinese content)
  4. pod install
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'FlutterNative' do
  flutter_application_path = '.. / your flutter folder /'
  eval(File.read(File.join(flutter_application_path, '.ios'.'Flutter'.'podhelper.rb')), binding)

end
Copy the code
3.2 Compiling Native content
  • Open the Native folder.xcworkspace
  • Close the Bitcode
  • If you compile the project, it usually doesBuild Success
3.3 Setting the Flutter Compilation Script
  • TARGET -> Build Phases -> Add the script
  • Enter the script content *** (content below) ***
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
Copy the code

Script content can be found in the corresponding path, interested students can read by themselves.

  • Move the script compilation location because Build Success is compiled in order to avoid unnecessary situations. Follow the figure below.

  • Command + B run

3.4 write code
  1. I am inMain.storyboardCreates a button and drags its click event toViewController.mIn the.
  2. Declare a FlutterViewController property variable
  3. In the click event, present the VC

FlutterViewController does not need to be created repeatedly. Once it is loaded, it will never be created again while the program is running! No! Release! Put! , repeated creation will lead to a larger memory footprint.

#import "ViewController.h"
#import <Flutter/Flutter.h>@interface ViewController () @property(strong,nonatomic)FlutterViewController *flutterVC; @end @implementation ViewController - (IBAction)FirstBtnClick:(id)sender { self.flutterVC = [FlutterViewController new];  [self presentViewController:self.flutterVC animated:YES completion:nil]; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. }Copy the code

#####3.5 Running the program (Xcode Project)

  • See what the Flutter project looked like when we created it

  • Try modifying the title in Flutter and re-run the Xcode project

At this point, we have completed the first step of Flutter interaction with Native! Build a bridge between Flutter and the Native.

We started to build on the code above and continue to write interactive code. ##### 1. Set the default route initialization page. 1

- (IBAction)FirstBtnClick:(id)sender { self.flutterVC = [FlutterViewController new]; // Set the initial route [_flutterVCsetInitialRoute:@"pageID"];
    [self presentViewController:self.flutterVC animated:YES completion:nil];
}
Copy the code

2. Set the default route name in the Flutter project

2.1 the import ‘dart: UI; 2.2. Declare a variable that receives the String final String pageIdentifier sent by Native; 2.3 Flutter prints the received content

import 'package:flutter/material.dart';
import 'dart:ui'; // Pass window.defaultroutename void main() => runApp(MyApp(pageIdentifier: window.defaultroutename,)); Class MyApp extends StatefulWidget {// Final String pageIdentifier; const MyApp({Key key, this.pageIdentifier}) : super(key: key); @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> {@override Widget Build (BuildContext context) {// Prints widget.pageIdentifierprint(widget.pageIdentifier);
    returnMaterialApp( home: Container( color: Colors.white, child: Center( child: Text(widget.pageIdentifier), ) , ) ); }}Copy the code

The effect is as follows:

Configure a different setInitialRoute at the Native endpoint. After the Flutter endpoint receives the setInitialRoute, different pages will be displayed based on the ID. Example code is as follows:

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    print(widget.pageIdentifier);

    switch(widget.pageIdentifier){
      case 'pageA': {return PageA();
      }
      case 'pageB': {return PageB();
      }
      case 'pageC': {return PageC();
      }
      defult:{
        returnDefalutPage(); }}}}Copy the code

Warning: Default initial route can only be set once! In subsequent iterations, the default route is the one that was entered the first time, regardless of what was passed. The reason is that Flutter is received with a variable modified with a final modifier, so if we want to change the initial route, we need to create a FlutterViewController again, which again consumes memory. So, if you want to jump to different screens, keep reading.


##### 2. Flutter transmits data to Native

Here we create a click event for PageID based on the above content. After clicking PageID, exit the Flutter interface and return to the Native interface.

1. Introduce services

import 'package:flutter/services.dart';

2. MethodChannel

2.1 The Flutter click event uses MethodChannel to pass data

child: GestureDetector(
    onTap: () {
      MethodChannel('test').invokeListMethod('dismiss'.'Let me write arguments here.');
    },
    child: Text(widget.pageIdentifier),
),
Copy the code

2.2 Native creates the FlutterMethodChannel and sets the callback

    self.flutterVC = [FlutterViewController new];
    
    FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"test" binaryMessenger:self.flutterVC];
    
    [channel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
        NSLog(@"%@ -- %@",call.method,call.arguments);
        if ([call.method isEqualToString:@"dismiss"]) { [self.flutterVC dismissViewControllerAnimated:YES completion:nil]; }}];Copy the code

2.3 Testing whether the channel is connected

As mentioned above, we have successfully obtained data transferred from the Flutter end to the Native end. Next, let’s try out how Native passes parameters to a Flutter via a MethodChannel.

##### 3. Native passes data to Flutter

Here we need to modify a lot of content, please be patient.

  1. Flutter create MethodChannel
  final MethodChannel _channerOne = MethodChannel('pageOne');
  final MethodChannel _channerTwo = MethodChannel('pageTwo');
  final MethodChannel _channerDefault = MethodChannel('pageDefault');
Copy the code
  1. Set variables to get interface name and initialization judgment
  var _pageName = ' ';
  var _initialized = false;
Copy the code
  1. Channel sets the channel callback
   @override
  void initState() {
    // TODO: implement initState
    super.initState();

    _channerOne.setMethodCallHandler((MethodCall call){
      print('This is One that receives the native callback ${call.method}==${call.arguments}');
       _pageName = call.method;
       setState(() {});
    });
    _channerTwo.setMethodCallHandler((MethodCall call){
      print('This is Two receiving the native callback ${call.method}==${call.arguments}');
      _pageName = call.method;
      setState(() {});
    });
    _channerDefault.setMethodCallHandler((MethodCall call){
      print('This is Default receiving the native callback ${call.method}==${call.arguments}');
      _pageName = call.method;
      setState(() {});
    });
  }
Copy the code

4. Set the build method

@override Widget Build (BuildContext context) {// If the Widget has not been initialized, the judgment rule uses the ID passed in during initialization. String switchValue = _initialized? _pageName:widget.pageIdentifier; // The flag is initialized _initialized =true;
    
    switch(switchValue){
      case 'pageOne': {return MaterialApp(
            home: Container(
              color: Colors.white,
              child: Center(
                child: GestureDetector(
                  onTap: () {
                    _channerOne.invokeListMethod('dismissOne'.'Here's the data from channel one.');
                  },
                  child: Text('This is page one.'(), ((), ((), ((). }case 'pageTwo': {return MaterialApp(
            home: Container(
              color: Colors.white,
              child: Center(
                child: GestureDetector(
                  onTap: () {
                    _channerTwo.invokeListMethod('dismissTwo'.'This is the data from channel 2.');
                  },
                  child: Text('This is page two.'(), ((), ((), ((). } default:{return MaterialApp(
            home: Container(
              color: Colors.white,
              child: Center(
                child: GestureDetector(
                  onTap: () {
                    _channerDefault.invokeListMethod('dismissDefault'.'This is the data coming back from the default channel.');
                  },
                  child: Text('This is the default'(), ((), ((), ((). }}}Copy the code
  1. Note the comments when you return to Native
@interface ViewControllerBOOL isSettedInitialRoute; BOOL isSettedInitialRoute; } @property(strong,nonatomic)FlutterViewController *flutterVC; @property(strong,nonatomic)FlutterMethodChannel *channelOne; @property(strong,nonatomic)FlutterMethodChannel *channelTwo; @property(strong,nonatomic)FlutterMethodChannel *channelDefault; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; Self. flutterVC = [FlutterViewController new]; self. FlutterViewController = [FlutterViewController new]; Self. ChannelOne = [FlutterMethodChannel methodChannelWithName:@"pageOne"binaryMessenger:self.flutterVC]; __weak typeof(self) weakself = self; // Register channel one call [_channelOnesetMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
        NSLog(@"Method: %@ -- arguments: %@",call.method,call.arguments);
        if ([call.method isEqualToString:@"dismissOne"]) { [weakself.flutterVC dismissViewControllerAnimated:YES completion:nil]; }}]; Self. ChannelTwo = [FlutterMethodChannel methodChannelWithName:@"pageTwo"binaryMessenger:self.flutterVC]; // Register channel two callback [_channelTwosetMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
        NSLog(@"Method: %@ -- arguments: %@",call.method,call.arguments);
        if ([call.method isEqualToString:@"dismissTwo"]) { [weakself.flutterVC dismissViewControllerAnimated:YES completion:nil]; }}]; Self. ChannelDefault = [FlutterMethodChannel methodChannelWithName:@"pageDefault"binaryMessenger:self.flutterVC]; // Register the default channel callback [_channelDefaultsetMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
        NSLog(@"Method: %@ -- arguments: %@",call.method,call.arguments);
        if ([call.method isEqualToString:@"dismissDefault"]) { [weakself.flutterVC dismissViewControllerAnimated:YES completion:nil]; }}]; } - (IBAction)FirstBtnClick:(id)sender {// if not already initializedif(! IsSettedInitialRoute) {// Set the initial interface [self.fluttervcsetInitialRoute: @"pageOne"]; IsSettedInitialRoute = YES; }else{// If a Flutter has already been initialized, call the methods registered with Flutter directly, [self.channelOne invokeMethod:@"pageOne" arguments:@"IOS sends a message to Flutter through a channel."]; } [self presentViewController:self.flutterVC animated:YES completion:nil]; } - (IBAction)SecondBtnClick:(id)sender {//if(! isSettedInitialRoute) { [self.flutterVCsetInitialRoute: @"pageTwo"];
        isSettedInitialRoute = YES;
    }else{
        [self.channelTwo invokeMethod:@"pageTwo" arguments:@"IOS sends messages to Flutter via channel 2"];
    }
    [self presentViewController:self.flutterVC animated:YES completion:nil];
}
Copy the code
  1. The interaction effect

We used MethodChannel to interact with Flutter and Native. Next, we will look at another Channel.

#####BasicMessageChannel is literally a channel for sending basic message data. It differs from the MethodChannel in a few ways. So let’s talk a little bit about this BasicMessageChannel

Sharp-eyed fans here will notice that this channel has one more codec parameter than MethodChannel, which can be read as “decoder”. So here we use StandardMessageCodec()

final BasicMessageChannel _basicMessageChannel = BasicMessageChannel('basic', StandardMessageCodec());
Copy the code

1.2 Registration method callback

@override
  void initState() {
    // TODO: implement initState
    super.initState();

    _basicMessageChannel.setMessageHandler((message){
      print($message == $message);
    });
  }
Copy the code

1.3 Calling the Message Channel to send messages to Native Because the code is long, the irrelevant parts are omitted

Container( height: 80, color: Colors.red, child: TextField( onChanged: (value){// Send TextField text to Native _basicMessagechannel.send (value); },),),Copy the code

2. The Native side declaration attributes @ 2.1 property (strong, nonatomic) FlutterBasicMessageChannel * basicChannel; 2.2 Initializing the Message Channel

    self.basicChannel = [FlutterBasicMessageChannel messageChannelWithName:@"basic" binaryMessenger:self.flutterVC];
Copy the code

2.3 Registration method Callback

    [_basicChannel setMessageHandler:^(id  _Nullable message, FlutterReply  _Nonnull callback) {
        NSLog(@"basicMessage == %@",message);
    }];
Copy the code

2.4 Native sends messages to Flutter through the message channel

- (IBAction)BasicClick:(id)sender {
    [self.basicChannel sendMessage:@"Send a message to Fultter."];
    [self presentViewController:self.flutterVC animated:YES completion:nil];
    
}
Copy the code

3. Interaction results

So that’s all about the interaction between Flutter and Native. Of course, there is another way of interaction here, EventChannel transmits the data stream. Please forgive me if I have the opportunity to make it up to you next time.