[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. Select
Build Phases
, click the + sign at the top and selectNew Run Script Phase
Then 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:
- [Fixed] Create a package script in the Flutter project that can generate the Flutter project products and upload them to the remote repository.
- 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 pluginbuild_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.sh
The script is similarflutter_copy_packages()
Get the Native plugin from the Flutter product and copy it to a specific file pathupload_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 release
install_release_flutter_app
Clone remote repository of Flutter products to local - Get debug products
install_debug_flutter_app
: Run build_ios.sh -m debug to package the Flutter project and get the debug product directory
- Get the release
- Introduce Flutter engineering products through POD
install_release_flutter_app_pod
: Traverses the Flutter product directory, usingpod sub, :path=>sub_abs_path
Rely 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