[TOC]

Hybrid engineering construction

In order for the project to support the mode of hybrid development of Flutter and Native, we need to access Flutter without invading the Native project, which directly depends on the PRODUCT of the Flutter project, as shown below:

The official Documentation of Flutter provides a hybrid solution

1. Create the Flutter project

Install the flutter and baidu by yourself. Execute flutter create -t module my_flutter in any directory. “my_flutter” is the name of the flutter project to be created.

2. Introduce Flutter into the existing Native project through Cocoapods

Add the following code to your Podfile

flutter_application_path = "xxx/xxx/my_flutter"
eval(File.read(File.join(flutter_application_path, '.ios'.'Flutter'.'podhelper.rb')), binding)
Copy the code

Then executing the Pod Install Ruby script does four things:

  • The Generated. Xcconfig file is in ‘my_flutter/.ios/Flutter/’. The file contains the Flutter SDK path, the Flutter project path, the Flutter project entry, and the build directory.
  • Add the Flutter. Framework from the Flutter SDK to the Native project via pod.
  • Add Native plug-ins that the Flutter project relies on via POD to the Native project
  • Use post_install to close Native project bitcode and add the ‘Generated. Xcconfig ‘file to Native project. #####3. Open the Xcode project and select the target that you want to add to the Flutter App. SelectBuild Phases, click the + sign at the top and selectNew Run Script PhaseThen enter the following script
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
Copy the code

The shell script for the flutter root directory is executed here:

  • Build: Builds the Flutter project according to the ‘configuration’ and other build configurations of the current Xcode project
  • Embed: Puts the built framework and resource packs into the Xcode build directory and signs the framework

There is a problem here. The Flutter project relies on The Native project for compilation, which affects the development process and packaging process of the Native project. The developer of the Flutter also needs to install the Flutter environment to debug the APP

4. To summarize

The above operation can be simply understood as: after the script of Native project is configured, the construction of the Flutter project will compile the Flutter project first. The Flutter project will generate the products including Flutter. Framework and dependent Native plug-ins in its corresponding directory, and finally configure the path and other parameters in POD. Integrates flutter in a pod native dependent manner.

To achieve non-invasive Native Flutter mixing engineering

Based on the official plan, in order to achieve this goal, the following two points need to be achieved:

  1. [Fixed] Create a package script in the Flutter project that can generate the Flutter project products and upload them to the remote repository.
  2. The pod used in Native engineering relies on the Flutter engineering products in remote warehouse; And retain the ability to rely on local Flutter project source code for easy debugging.
1. Package script of the Flutter project

Add the build_ios.sh file to the project directory to automatically package the Flutter project into the following steps:

  • flutter_get_packages()Check the Flutter environment and pull the Flutter plugin
  • build_flutter_app()Compile the Flutter project to get the product and copy it to a specific file path, the main logic and official providedxcode_backend.shThe script is similar
  • flutter_copy_packages()Get the Native plugin from the Flutter product and copy it to a specific file path
  • upload_product()Release mode synchronously uploads artifacts to Git

Run./build_ios.h -m debug. /build_ios.h -m release to get the products of different environments and upload them to the remote repository

2.Native depends on Flutter products

In this part, we need to achieve the capture of the Flutter project release and integrate it into the Native project, while retaining the ability to rely on the local Flutter project. Add the flutterhelper.rb script to your native project as follows:

  • Obtain Flutter engineering products
    • Get the releaseinstall_release_flutter_appClone remote repository of Flutter products to local
    • Get debug productsinstall_debug_flutter_app: Run build_ios.sh -m debug to package the Flutter project and get the debug product directory
  • Introduce Flutter engineering products through PODinstall_release_flutter_app_pod: Traverses the Flutter product directory, usingpod sub, :path=>sub_abs_pathRely on Flutter.FrameWork, Native plug-ins, etc

The configuration in podfile is as follows:

# is true, debug is false, and release is released
FLUTTER_DEBUG_APP=true
# If FLUTTER_APP_PATH is specified, this configuration is invalid
FLUTTER_APP_URL= "http://appinstall.aiyoumi.com:8282/flutter/iOS_flutter_product.git"
# flutter git branch, default to master
# If FLUTTER_APP_PATH is specified, this configuration is invalid
FLUTTER_APP_BRANCH="master"
Git configuration is invalid if there is a value for flutter local project directory, absolute or relative
FLUTTER_APP_PATH="/Users/zouyongfeng/ac_flutter_module"

eval(File.read(File.join(__dir__, 'flutterhelper.rb')), binding)

Copy the code

Finally, configure the packing job in Jenkins as follows:

cd ${WORKSPACE}
if[[!-d "${FLUTTER_PROJECT_Name}"]].then
  git clone ${FLUTTER_PROJECT_GIT_REPO} ${FLUTTER_PROJECT_Name} -b ${PROJECT_GIT_BRANCH}
fi

if[[!-d "${FLUTTER_PRODUCT_Name}"]].then
  git clone ${FLUTTER_PRODUCT_GIT_REPO} ${FLUTTER_PRODUCT_Name} -b ${PROJECT_GIT_BRANCH}
fi

cd ${WORKSPACE}/${FLUTTER_PRODUCT_Name}
git fetch
git reset --hard
git checkout ${PROJECT_GIT_BRANCH}
git pull --no-commit --all

cd ${WORKSPACE}/${FLUTTER_PROJECT_Name}
git fetch
git reset --hard
git checkout ${PROJECT_GIT_BRANCH}
git pull --no-commit --all
source ~/.bash_profile
sh build_ios.sh -m release
Copy the code

Interact with native practices

Flutter official hybrid solution

1.Flutter calls native

Flutter provides the FlutterMethodChannel that allows Flutter to invoke native methods as follows:

FlutterViewController* FlutterViewController = [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil]; [flutterViewControllersetInitialRoute:@"myApp"]; __weak __typeof(self) weakSelf = self; Dart NSString *channelName = @"com.pages.your/native_get";
FlutterMethodChannel *messageChannel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:flutterViewController];
    [messageChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
    if ([call.method isEqualToString:@"iOSFlutter"]) {
            TargetViewController *vc = [[TargetViewController alloc] init];
            [self.navigationController pushViewController:vc animated:YES];
            if (result) {
                result(@"Contents returned to flutter"); }}}]; Static const methodChannel = const methodChannel ()'com.pages.your/native_get');
_iOSPushToVC() async {
    dynamic result;
    result = await methodChannel.invokeMethod('iOSFlutter'.'parameters');
  }

Copy the code
2. Native invocation of Flutter

Flutter provides the FlutterEventChannel to make native calls to Flutter

/ / in the native FlutterEventChannel * evenChannal = [FlutterEventChannel eventChannelWithName: channelName binaryMessenger:flutterViewController]; // Agent FlutterStreamHandler [evenChannalsetStreamHandler:self];
#pragma mark - <FlutterStreamHandler>// This onListen is the callback when the Flutter end starts to listen to this channel. The second EventSink parameter is the carrier used to transmit data. - (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(FlutterEventSink)events { // Arguments Flutter gives the parameters to nativeif (events) {
        events(@"Push to the VC of flutter");
    }
    returnnil; Static const EventChannel EventChannel = const EventChannel('com.pages.your/native_post'); / / to monitor events, at the same time send parameters eventChannel receiveBroadcastStream (12345). Listen (_onEvent, onError: _onError); String naviTitle ='title'; Void _onEvent(Object event) {setState(() {
    naviTitle =  event.toString();
  });
}
Copy the code
3. Summary

This is the official mixed development solution. This solution has a huge disadvantage, which is that the memory increases when the native and Flutter pages are superimposed, because the FlutterView and FlutterViewController create a new object every time they jump. The more Flutter pages you create, the memory will explode, especially on iOS.

Flutter_boost hybrid scheme

1. Introduction

Flutter View
Native
Flutter View

The page stack is completely controlled by the native. Each flutter page corresponds to a native container (ViewController and Activity). The native end creates a FlutterRouter to implement the interface in the FLBPlatform. Both flutter and native calls to each other implement the openPage interface in FlutterRouter. The code is as follows:

// iOS: FlutterRouter
- (void)openPage:(NSString *)name params:(NSDictionary *)params animated:(BOOL)animated completion:(void (^)(BOOL finished))completion {
    [ACRouter openWithURLString:name userInfo:params completion:^(ACRouterOutModel * _Nonnull outModel) {
        [FlutterBoostPlugin.sharedInstance onResultForKey:[params objectForKey:requestIdKey] resultData:outModel.data params:@{}];
        if(completion) completion(YES);
    }];
 
}
Copy the code

The flutter side establishes an ACRouter to encapsulate flutterBoost, and the flutter jumps to the native page and directly calls the route in the native project

// Pass protocol name and page initialization parameter acrouter.openurl ("mizlicai://product/normalProductDetail", {'serial': 'PI_11221'}, routeCallback: (Map<dynamic, dynamic> result) {// Process the callback resultprint("did recieve second route result $result"); }); // Native: // TODO: common product details [ACRouter registerWithURLString:@"mizlicai://product/normalProductDetail" handler:^(NSDictionary * _Nullable paramsIn) {
        ProductDetailViewController *vc = [[ProductDetailViewController alloc] init];
        vc.serial = [paramsIn valueForKey:@"serial"];
        vc.origin = [paramsIn valueForKey:@"origin"];
        [[UIViewController mz_topController].navigationController pushViewController:vc animated:YES];
    }];
Copy the code

The flutter end and native open flutter page

// native [ACRouter registerWithURLString:@"mizlicai://flutter/open" handler:^(NSDictionary * _Nullable paramsIn) {
 NSMutableDictionary *params = [[NSMutableDictionary alloc] initWithDictionary:paramsIn[@"params"]];
 
 FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
 [vc setName:paramsIn[@"pageName"] params:params];
 [[UIViewController mz_topController].navigationController pushViewController:vc animated:animated];
 ACRouterCompletionBlock action = paramsIn[ACRouterParameterCompletion];
if(action) { ACRouterOutModel *outModel = [[ACRouterOutModel alloc] init]; action(outModel); }}]; / / in the flutter ACRouter. OpenUrl ("mizlicai://flutter/open", {'pageName': 'userCenter'.'params':{}, routeCallback: (Map<dynamic, dynamic> result) {// Process callback resultsprint("did recieve second route result $result");
 });
Copy the code

#####2. Protocol support Flutter can invoke the native project componentized routing protocol (Mijiazhuang iOS routing protocol) to jump to native pages and invoke native interfaces, etc. #####3. To keep the same logic as the native request framework, request tools are encapsulated in abstract classes. When Flutter starts, determine the environment and use real request classes or Mock request classes.

// main.dart
if (ApiClient.isProduction) {
      ApiClient.request = RealRequest();
    } else {
      ApiClient.request = MockRequest();
  }
Copy the code

MockRequest and RealRequest implement the parent send methods, with RealRequest invoking the natively initiated network request via ACRouter, and MockRequest parsing the local JSON

Apiclient.request. send(api.userCenter, httprequest. GET, {}, (Map Response) {}); // RealRequest void send(String url, String requestType, Map param, Function callback) { param.addAll({'url': url, 'requestType': requestType});
    ACRouter.openUrl(RouteCst.httpFlutterRequest, param,
        routeCallback: (Map<dynamic, dynamic> result) {
      callback(result);
    });
  }
 
// MockRequest
void send(String url, String requestType, Map param, Function callback) {
    dynamic responseJson =
        MockRequest.mock(action: getJsonName(url), param: param);
    callback(responseJson);
  }
Copy the code
4. Navigation

The Stack of Flutter pages is controlled natively, using its own navigation bar. Methods to close different pages

Static Future<bool> closeCurPage() static Future<bool> closeCurPage('mizlicai://product/closeToRoot', param,
        routeCallback: (Map<dynamic, dynamic> result) {
      callback(result);
    });
Copy the code
5. Native access

Add configuration to Podfile to switch local, remote, debug, etc


platform :ios, '9.0'

# is true, debug is false, and release is released

FLUTTER_DEBUG_APP=false

# If FLUTTER_APP_PATH is specified, this configuration is invalid

FLUTTER_APP_URL= "http://appinstall.aiyoumi.com:8282/flutter/iOS_flutter_product.git"

# flutter git branch, default to master

# If FLUTTER_APP_PATH is specified, this configuration is invalid

FLUTTER_APP_BRANCH="master"

Git configuration is invalid if there is a value for flutter local project directory, absolute or relative

FLUTTER_APP_PATH="/Users/zouyongfeng/ac_flutter_module"

eval(File.read(File.join(__dir__, 'flutterhelper.rb')), binding)

Copy the code

AppDelegate, initialize FlutterBoost and pass in FlutterRouter

#import "FlutterRouter.h"
- (void)startFlutter {

    [FlutterBoostPlugin.sharedInstance startFlutterWithPlatform:[FlutterRouter sharedRouter]

                                                        onStart:^(FlutterViewController *fvc) {
                                                        }];

}
Copy the code