One, foreword

The previous note described how to mix Flutter and Android. This note introduces the application of Flutter and Android.

Read the official Documentation of Flutter to get an overview of the key steps in the mix of iOS and Flutter. This includes compiling all Flutter files into a static library framework and managing them through CocoaPods. The following is a record of my convenient script use process shared by the official documents and excellent online authors.

Create the Flutter module and compile it into the framework

Those of you who read the previous article on Flutter and Android Development Practices know that Flutter can be referenced before it is compiled into an AAR in Android. Unlike Android, iOS can only access the Flutter module as a framework. The general steps are as follows:

  • Step 1: Create a vmiOSEngineering, useCocoaPodsManagement engineering
  • Step 2: Create a vmFlutter Module
  • Step 3: TheFlutter ModuleCompiled intoframework, the introduction ofiOSengineering
  • Step 4: Write the test code and view the results

iOS_Flutter_MixBuilder
CocoaPods


Step 1: Create an iOS project and use CocoaPods to manage the project

Create a new Xcode project under iOS_Flutter_MixBuilder Open Xcode and select Create a new Xcode project -> Select Single View App -> name the project iOS_app -> select the newly created folder (iOS_Flutter_MixBuilder), Create.









The startup terminal runs the following commands in iOS_Flutter_MixBuilder/ iOS_app:

pod init
pod install
Copy the code


Step 2 create a Flutter Module

Run the following command to switch to the iOS_Flutter_MixBuilder directory:

flutter create -t module my_flutterCopy the code

My_flutter is the name of the module.



At this point we will look at the Flutter Module file directory and first show the hidden file (in macOS Sierra and above (Mojave), we can use the shortcut to ⌘⇧ (Command + Shift +.). To quickly show and hide hidden files.



Hide folders. Ios files do not need to be changed and will be regenerated after every flutter Clean, flutter Packages get operation. Execute by adding any plugin references to the Flutter Module

flutter pub 
getWill be generated after
aPodfileFile.

Open the pubspec.yaml file, which manages the dependencies of the Flutter plugin. Add data persistence plug-in dependencies to save files. Switch your terminal to the my_flutter directory and run the following command:

cd /Users/admin/Desktop/WJJ_WJJ/iOS_Flutter_MixBuilder/my_flutterCopy the code

flutter pub getCopy the code







Note: in order to prevent no scientific Internet cause flutter pub get operation often jammed, we can change the image of China, https://flutter.io/community/china, replace operation:

vi ~/.bash_profile Copy the code

And then add

export PUB_HOSTED_URL=https://pub.flutter-io.cnCopy the code

export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cnCopy the code

Then save the Settings and restart the terminal.

Some students will find that restarting the terminal performs any
FlutterCommand prompt
Waiting for another flutter command to release the startup lock…In you
FlutterIn the package to delete
flutter/bin/cache/lockfilefile
.



Step 3: Compile the Flutter Module into the Framework and introduce it into the iOS project

There are two ways to do this:

  • Method 1: Add the following code to the iOS project podfile: my_flutter is the name of the Flutter Module

flutter_application_path = '.. /my_flutter/'

load File.join(flutter_application_path, '.ios'.'Flutter'.'podhelper.rb')

install_all_flutter_pods(flutter_application_path)
Copy the code



Go to the iOS project directory iOS_app and run:

pod installCopy the code

Open the iOS project and you can see that the files related to Flutter have been imported into the iOS project.



The advantage of this method is that it can be done in one step and is officially recommended as the first method. However, it requires that every developer that references the framework should have the Flutter environment on their PC, and the framework that needs to be referenced should be distributed in different folders, which is cumbersome to view and obviously not friendly.

  • Method 2: Import the build products of the Flutter Module into the project via CocoaPods

Compile the Flutter Module first, select Debug or Release mode. Note: you should add — no-coDesign to prevent failed compilations caused by certificates.

Switch to the my_flutter folder and execute the following command (choose one of two as required) :

Flutter build ios --debug -- no-coDesignCopy the code
Flutter build ios --release -- no-coDesignCopy the code

After the success of the command execution watch folder change, build/ios/Debug – FlutterPluginRegistrant iphoneos folder to store the compiled products. Framework and shared_preferences) framework.



The two frameworks above are generated by the compilation of the Flutter code. In addition, we need a library containing the Flutter resources, app. framework, which is located in the.ios/Flutter/ directory, And the engine runtime for Flutter, Flutter. Framework, which is in the.ios/Flutter/engine/ directory.

Since CocoaPods can’t manage framework files directly, we need to simply seal the four files found above into aPod library.

Create a Pod library named Flutter_lib after the terminal cuts to iOS_Flutter_MixBuilder and runs the following command to answer a wave of soul torturing:

pod lib create flutter_libCopy the code



When the Pod library is successfully created, the example project will pop up automatically. Close it and observe the file directory.



The flutter_lib.podspec file is the Pod library configuration file. In this file, you can specify the Pod library version and the address of the library content file.



Now all you need to do is put the four Flutter library files in a new folder iOS_Frameworks under the /flutter_lib/ directory and specify the path to iOS_Frameworks in the flutter_lib.podspec file. Manual efficiency is too low, here learn from the author: a simple script to automate processing.



Place the script buildfile. sh into the my_flutter folder



The terminal cuts to the my_flutter folder and runs the following command to make the script work:

sh build_file.shCopy the code



Check the file directory after the script runs successfully. The required framework has appeared in the specified location.



Podspec: Flutter_lib. podspec: flutter_lib.podspec: flutter_lib.podspec: flutter_lib.podspec



Now that the Flutter Framework Pod library has been created, you just need to import the library locally in the iOS project. Open iOS_Flutter_MixBuilder/ iOS_app /Podfile and add the following code:

pod 'flutter_lib', :path => '.. /flutter_lib'Copy the code



Go to iOS_Flutter_MixBuilder/ iOS_app and run the Pod install command



After the command is successful, open the iOS project to check. At this time, the Pod library we made has been successfully introduced into the iOS project. Build the iOS project, success!




Step 4: Write the test code and view the results

We use the Flutter code used in Android article and try the four classic scenarios listed in Android article:

  • IOS page Open the Flutter page and upload values
  • The Flutter page opens the iOS page and uploads values
  • The iOS page returns to the Flutter page and uploads values
  • The Flutter page returns to the iOS page and uploads values

We need new page has a native in iOS FirstNativeViewController, SecondNativeViewController. Unlike android, we don’t need to create a new shell native page, FirstFlutterActivity. Because iOS allows us to instantiate the Flutter controller object: FlutterViewController. Android instantiates the Flutter view: FlutterView, which requires a shell controller, FirstFlutterActivity, to host it.

Before starting the above scene, the Flutter page defines the following:

  1. Add a textView to display content from other pages
  2. Add a button to open the next native page
  3. Add a button to return to the previous native page

There are two ways to display a Flutter page in iOS:

  1. Instantiate a FlutterViewController object -> specify a route for the FlutterViewController -> push/present to the FlutterViewController
  2. Instantiate a FlutterEngine -> Specify a route for the FlutterEngine -> FlutterEngine run -> Create a FlutterViewController using the FlutterEngine -> A push/present to FlutterViewController



Pay attention to: In method 2, is
FlutterEngineThe specified route must be in
FlutterEngine runBefore, otherwise, the route is invalid.


Easter egg: In the current version of Flutter, the routes carried by FlutterEngine are changed to “/”, which should be a bug of Flutter. The current method of Flutter creation using FlutterEngine cannot specify specific routes.


Introduction of key nouns:

FlutterViewController: a Flutter page controller that we can either push/present directly to or embed into our pages as a ChildViewController.

FlutterEngine: The engine that Flutter is responsible for executing the Dart code on iOS, rendering the UI code written by Flutter into the FlutterViewController.


Next, we open the Flutter in the FirstNativeViewController page and complete the communicate with the native. In order to facilitate our defined FirstNativeViewController global properties.

@property (nonatomic, strong) FlutterViewController *flutterViewController;Copy the code

Do not use
FlutterEngineOpen the
FlutterThe page code is as follows:

/ / initialize FlutterViewController self. FlutterViewController = [[FlutterViewController alloc] init]; / / specify routing and routing carry parameters for FlutterViewController [self. FlutterViewControllersetInitialRoute:@"route1? {\"message\":\" Hey, this article is from the first native page. You will see me \"}" on the Flutter page.]; / / set the mode to jump full screen self. FlutterViewController. ModalPresentationStyle = UIModalPresentationFullScreen; [self presentViewController:self.flutterViewController animated:YES completion:nil];Copy the code

use
FlutterEngineOpen the
FlutterThe page code is as follows:


// Initialize FlutterEngine FlutterEngine * FlutterEngine = [[FlutterEngine alloc]initWithName:@"FirstFlutterViewController"]; FlutterEngine navigationChannel [flutterEngine navigationChannel] invokeMethod:@. FlutterEngine navigationChannel [flutterEngine navigationChannel] invokeMethod:@"setInitialRoute" arguments:@"route1? {\"message\":\" Hey, this article is from the first native page. You will see me \"}" on the Flutter page.]; [FlutterEngine run] [FlutterEngine run] [FlutterEngine run] / / use FlutterEngine initialization FlutterViewController self. FlutterViewController = [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil]; / / set the mode to jump full screen self. FlutterViewController. ModalPresentationStyle = UIModalPresentationFullScreen; [self presentViewController:self.flutterViewController animated:YES completion:nil];Copy the code

The Dart code for Flutter is as follows:

Parses the route to obtain the data carried this time

void main() => runApp(_widgetForRoute(window.defaultRouteName)); Widgetforroute (String url) {// Route name String route = url.indexof ('? ') = = 1? url : url.substring(0, url.indexOf('? ')); // Parameter Json String String paramsJson = url.indexof ('? ') = = 1?'{}' : url.substring(url.indexOf('? ') + 1);
  Map<String, dynamic> mapJson = json.decode(paramsJson);  String message = mapJson["message"]; Switch (route) {case 'route1':
      return MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: Text('Flutter page'),
          ),
          body: Center(child: Text('Page name: $route',style: TextStyle(color: Colors.red), textDirection: TextDirection.ltr),),
        ),
      );
    default:
      return Center(
        child: Text('Unknown route: $route',style: TextStyle(color: Colors.red), textDirection: TextDirection.ltr),
      );
  }}Copy the code

To complete the above code can open the Flutter in FirstNativeViewController page, here is how iOS and Flutter interactions:

We are familiar with the traditional H5 page and native interaction, through the intermediate communication tool object, defined methods or properties to communicate. Flutter also has a Platform Channel for native interaction with iOS. There are three types of Flutter communication objects:

  • MethodChannel: The most common method pass-through, which helps Flutter and native platforms call methods to each other, is also the focus of this tutorial.
  • BasicMessageChannel: Used to transfer data information.
  • EventChannel: used for events listening and transmission.

In the introduction above, we can open the Flutter page in an iOS page. Then we just need to send commands to the Flutter via MethodChannel and receive callbacks to the message. Then we can display some data uploaded by the other party on iOS or Flutter page. On the whole!

IOS code is as follows (the following code is still written in FirstNativeViewController) :

Let’s start with a MethodChannel by defining it as unique. Note: Here we define two methodChannels, one for sending messages to Flutter and one for receiving callback messages to Flutter.

Static NSString *CHANNEL_NATIVE = @"com.example.flutter/native"; //Native sends messages to Flutter static NSString *CHANNEL_FLUTTER = @"com.example.flutter/flutter";Copy the code

Class using the defined name
MethodChannel.Note:
MethodChannelThe initialization method takes two parameters. The first parameter
BinaryMessenger
Messenger,We can think of it as theta
MethodChanneland
FlutterPage binding items, through
flutterViewController.binaryMessengeror
flutterEngine.binaryMessengerWe can all get constructs
MethodChannelThe first argument to. The second parameter needs to be passed in a unique name that we defined earlier.


IOS receives messages from Flutter

receive
FlutterThe message has to be initialized first
MethodChannelAnd use the name defined previously
CHANNEL_NATIVE. We can see this in the following code
MethodChannelThe callback parameters are:
FlutterMethodCall
 call,
FlutterResult 
result.
callCan provide us with this
FlutterThe name of the method being sent (
call.method) can also be provided this time
FlutterThe parameters carried by the method being sent (
call.
arguments).
resultIs a
blockThe callback
.We can call this after we’re done with the logic
blockinform
FlutterOur results.


// Initialize messageChannel, CHANNEL_NATIVE is the unified communication signal of iOS and Flutter. FlutterMethodChannel *messageChannel = [FlutterMethodChannel methodChannelWithName:CHANNEL_NATIVE binaryMessenger:self.flutterViewController.binaryMessenger]; // Accept a Flutter callbacksetMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
        __strong __typeof(weakSelf) strongSelf = weakSelf;
        if ([call.method isEqualToString:@"openSecondNative"] {// open the second native page NSLog(@"Open the second native page");
            strongSelf.sMessageFromFlutter = call.arguments[@"message"]; [strongSelf pushSecondNative]; Tell Flutter our processing resultif (result) {
                result(@"Successfully opened the second native page"); }}else if ([call.method isEqualToString:@"backFirstNative"] {// return the first native page NSLog(@"Return to first native page");
            [strongSelf backFirstNative];
            strongSelf.lblTitle.text = call.arguments[@"message"]; Tell Flutter our processing resultif (result) {
                result(@"First native page returned successfully"); }}}];Copy the code

/ / open the second native page - (void) pushSecondNative {SecondNativeViewController * secondNativeVC = [[SecondNativeViewController alloc]initWithNibName:@"SecondNativeViewController"bundle:nil]; secondNativeVC.showMessage = self.sMessageFromFlutter; __weak __typeof(self) weakSelf = self; / / the second block of native page callback secondNativeVC. ReturnStrBlock = ^ (nsstrings * message) {__strong __typeof strongSelf = (weakSelf) weakSelf; / / notify Flutter after you come back from the second native page page updated copy [strongSelf sendMessageToFlutter: message]; }; secondNativeVC.modalPresentationStyle = UIModalPresentationFullScreen; // The s controller of the current screen is FlutterViewController, So you should use the self. To jump flutterViewController [self. FlutterViewController presentViewController: secondNativeVC animated: YES completion:nil]; }Copy the code

- (void) backFirstNative {/ / close the Flutter page [self. FlutterViewController dismissViewControllerAnimated: YES completion: nil]; }Copy the code

Note: In the example iOS involves using blocks to handle callbacks between viewControllers (e.g., ReturnStrBlock).


IOS sends messages to Flutter

Sending messages to Flutter also initializes a MethodChannel with the previously defined name CHANNEL_FLUTTER. Use the MethodChannel method invokeMethod to send this message to Flutter!

- (void)sendMessageToFlutter:(NSString *)message{// initialize messageChannel, CHANNEL_FLUTTER is the unified communication signal of iOS and Flutter. FlutterMethodChannel *messageChannel = [FlutterMethodChannel methodChannelWithName:CHANNEL_FLUTTER binaryMessenger:self.flutterViewController.binaryMessenger]; [messageChannel invokeMethod:@"onActivityResult" arguments:@{@"message":message}]; }Copy the code

The code for the iOS side of the interaction is described above. The code for the Flutter side is described below. As follows:

First of all, we’re in the original
main.dartI’ll do an extension in the file. To define a
WidgetUsed to display
iOSPass in the data and create a button to
iOSSend the message. with
iOSEnd,
main.dartWe also define the same name in the file
MethodChannel. Note: we are
Widgetthe
initState()It should be in the method
MethodChannelListen to the code. We can do it at
Flutterthe
MethodChannelThe callback method in the
Call the method,
call.method.
argumentsTo know,
iOSWhat method you want to call this time, and what parameters you bring in.


class ContentWidget extends StatefulWidget{
  ContentWidget({Key key, this.route,this.message}) : super(key: key);
  String route,message;
  _ContentWidgetState createState() => new _ContentWidgetState();
}
class _ContentWidgetState extends State<ContentWidget>{
  static const nativeChannel = const MethodChannel('com.example.flutter/native');
  static const flutterChannel = const MethodChannel('com.example.flutter/flutter');
  void onDataChange(val) {
    setState(() {
      widget.message = val;
    });
  }
  @override
  void initState(){
    super.initState();
    Future<dynamic> handler(MethodCall call) async{
      switch (call.method){
        case 'onActivityResult':
          onDataChange(call.arguments['message']);
          print('1234'+call.arguments['message']);
          break;
      }
    }
    flutterChannel.setMethodCallHandler(handler);
  }
  Widget build(BuildContext context) {
    // TODO: implement build
    return Center(
      child: Stack(
        children: <Widget>[
          Positioned(
            top: 100,
            left: 0,
            right: 0,
            height: 100,
            child: Text(widget.message,textAlign: TextAlign.center,),
          ),
          Positioned(
            top: 300,
            left: 100,
            right: 100,
            height: 100,
            child: RaisedButton(
                child: Text('Open previous native page'),
                onPressed: (){
                  returnLastNativePage(nativeChannel);
                }
            ),
          ),
          Positioned(
            top: 430,
            left: 100,
            right: 100,
            height: 100,
            child: RaisedButton(
                child: Text('Open next native page'), onPressed: (){ openNextNativePage(nativeChannel); }),)],),); }}Copy the code

The above code is missing methods: returnLastNativePage, openNextNativePage. As follows:

You will remember that we could call the Result block when our iOS page received a Flutter callback to tell the Flutter page what we had done. Yes, we asynchronously get the information for these callbacks and print it in the following two methods.

Future<Null> returnLastNativePage(MethodChannel channel) async{
  Map<String, dynamic> para = {'message':'Hi, this article is from the Flutter page, go back to the first native page and see me'};
  final String result = await channel.invokeMethod('backFirstNative',para);
  print('This is printed in the Flutter'+ result);
}Copy the code

Future<Null> openNextNativePage(MethodChannel channel) async{
  Map<String, dynamic> para = {'message':'Hi, this article is from the Flutter page, open the second native page and see me'};
  final String result = await channel.invokeMethod('openSecondNative',para);
  print('This is printed in the Flutter'+ result);
}Copy the code

At this point,
iOS,
FlutterWe can exchange what we need. If you find that when you compile
main.dartIn the
MethodChannelError, then you must not import the correct header file such as:
import ‘package:flutter/services.dart’.

Note: After you change the contents of the Flutter file, re-run the build_file. Sh script described above and switch to the iOS_app folder to install the pod library.

The previous attempts are based on Flutter1.12. If your Flutter version < 1.12, please update the Flutter version first.


— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — complete code address — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Function Code Address:

https://github.com/JJwow/iOS_Flutter_MixBuilder.gitCopy the code

Pod library address:

https://github.com/JJwow/flutter_lib.gitCopy the code