Flutter与Native混合开发-FlutterBoost集成应用和开发实践(iOS)

借图Flutter_Boost 背景: 现在Flutter火热,各个公司都会尝试使用Flutter开发,一般采用的都会是混合形式的开发,混合开发目前Flutter官方提供的不太完善、iOS和Android有差异性、接口也不同意、很麻烦,也有一些大厂为了实现高效率高可用自己实现一套混合开发技术,比如闲鱼的flutter_boost和哈罗的flutter_thrio。两者之间优缺点及设计原理、架构等后续再来具体分析。接下我们先说下flutter_boost在iOS项目中的接入混合实践。 准备工作: 1.Flutter SDK 为了便于后续我们flutter sdk版本的快速切换,我们需要安装管理工具,方便我们在后续开发或者调试过程中快速切换版本。 这里推荐使用的Flutter SDK管理工具fvm,能快速切换版本,具体不做介绍,直接去查看文档就可以,很详细。 安装自己所需的flutter sdk版本(我这里安装的是1.17.1版本,因为需要跟下边的flutter_boost版本对应): fvm-flutter 2.flutter_boost框架 了解混合框架flutter_boost,首先需要知道我们需要使用他们的那个版本及版本对应的flutter sdk版本,比如:flutter_boost:v1.17.1-hotfixes 对应的flutter sdk:1.17.1 。 正式接入: 一、flutter_module工程 1.创建空文件夹后,使用命令: flutter create -t module flutter_module 如果iOS使用的是Swift开发使用命令: flutter create -i swift -t module flutter_module 2.flutter_module创建完成后,打开flutter_module文件夹下pubspec.yaml文件。如下: pubspec.yaml 注意:这里我用的flutter_boost的SDK最新版本:v1.17.1-hotfixes 3.cd到flutter_module文件夹下,执行命令: flutter packages get 包下载完成后flutter_module工程基本配置已经完成。 二、iOS原生工程 1.创建iOS项目 注意:iOS工程根目录与flutter_module根目录平级。 2.cd到iOS工程目录下创建Podfile文件,执行命令: touch Podfile 生成文件,然后可以使用vim Podfile 编辑或者直接open Podfile 或者直接打开文本编辑也可以。 在文件中添加 flutter_application_path = '../flutter_module' load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb') install_all_flutter_pods(flutter_application_path) pod 3.执行 pod install下载依赖库。 pod 4.配置项目 如果是OC工程,则不会要做什么额外处理,如果是Swift项目需要建个桥接文件FlutterHybridiOS-Bridging-Header.h,同时Build Sttings中需要配置该文件路径。 swift调用OC桥接文件 对应路径 注意: (1).看其他博客出现过,查看Build Phases中是否存在Flutter Build Script的脚本,如果不存在需要添加对应脚本,但是目前我没出现缺失的情况。 生成的Flutter Build Script "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed 三.代码接入实践 这块可以直接下载flutter_boost官方例子OC,flutter_boost官方例子Swift查看具体实践,其中OC例子是最全的,这里只已Swift项目为基础介绍基础使用。 <1>.iOS平台 1.创建实现flutter_boost路由类,实现FLBPlatform中的协议,和Flutter中也会有对应的方法来处理跳转操作。(直接参考上边官方的代码类就行),即push模式跳转、present模式跳转、关闭页面等方法。 import Foundation //import flutter_boost class PlatformRouterImp: NSObject, FLBPlatform { func open(_ url: String, urlParams: [AnyHashable : Any], exts: [AnyHashable : Any], completion: @escaping (Bool) -> Void) { //跳转打开原生Native页面 if (url == "native") { self.openNativeVC(url, urlParams: urlParams, exts: exts) return } var animated = false; if exts["animated"] != nil{ animated = exts["animated"] as! Bool; } let vc = FLBFlutterViewContainer.init(); vc.setName(url, params: urlParams); self.navigationController().pushViewController(vc, animated: animated); completion(true); } func present(_ url: String, urlParams: [AnyHashable : Any], exts: [AnyHashable : Any], completion: @escaping (Bool) -> Void) { var animated = false; if exts["animated"] != nil{ animated = exts["animated"] as! Bool; } let vc = FLBFlutterViewContainer.init(); vc.setName(url, params: urlParams); navigationController().present(vc, animated: animated) { completion(true); }; } func close(_ uid: String, result: [AnyHashable : Any], exts: [AnyHashable : Any], completion: @escaping (Bool) -> Void) { var animated = false; if exts["animated"] != nil{ animated = exts["animated"] as! Bool; } let presentedVC = self.navigationController().presentedViewController; let vc = presentedVC as? FLBFlutterViewContainer; if vc?.uniqueIDString() == uid { vc?.dismiss(animated: animated, completion: { completion(true); }); }else{ self.navigationController().popViewController(animated: animated); } } private func openNativeVC( _ name: String?, urlParams params: [AnyHashable : Any]?, exts: [AnyHashable : Any]? ) { let vc = UIViewController() let animated = (exts?["animated"] as? NSNumber)?.boolValue ?? false if (params?["present"] as? NSNumber)?.boolValue ?? false { self.navigationController().present(vc, animated: animated) {} } else { self.navigationController().pushViewController(vc, animated: animated) } } func navigationController() -> UINavigationController { let delegate = UIApplication.shared.delegate as! AppDelegate let navigationController = delegate.window?.rootViewController as! UINavigationController return navigationController; } } 2.flutter_boost初始化,即在启动的时候初始化框架代码。 import UIKit @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. //路由创建 let router = PlatformRouterImp.init(); //FlutterBoost初始化 FlutterBoostPlugin.sharedInstance().startFlutter(with: router, onStart: { (engine) in }); self.window = UIWindow.init(frame: UIScreen.main.bounds) let viewController = ViewController.init() let navi = UINavigationController.init(rootViewController: viewController) self.window.rootViewController = navi self.window.makeKeyAndVisible() return true } 3.具体使用(创建自己的ViewController,添加两个按钮进行测试跳转Native) import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = UIColor.white // Do any additional setup after loading the view. let btn = UIButton(type: .custom); btn.backgroundColor = UIColor.red btn.frame = CGRect(x: 10, y: 60, width: 60, height: 40) btn.setTitle("Push Flutter Page", for: .normal) self.view.addSubview(btn); btn.addTarget(self, action: #selector(onClickPushFlutterPage), for: .touchUpInside); let btn2 = UIButton(type: .custom); btn2.backgroundColor = UIColor.blue btn2.frame = CGRect(x: 10, y: 120, width: 60, height: 40) self.view.addSubview(btn2); btn2.setTitle("Present Flutter Page", for: .normal) btn2.addTarget(self, action: #selector(onClickPresentFlutterPage), for: .touchUpInside); } @objc func onClickPushFlutterPage(_ sender: UIButton, forEvent event: UIEvent){ // self.navigationController?.navigationBar.isHidden = true //其中这里的first,在Flutter也会对应初始化的时候注册该标识对应的Widget。 FlutterBoostPlugin.open("first", urlParams:[kPageCallBackId:"MycallbackId#1"], exts: ["animated":true], onPageFinished: { (_ result:Any?) in print(String(format:"call me when page finished, and your result is:%@", result as! CVarArg)); }) { (f:Bool) in print(String(format:"page is opened\(f)")); } } @objc func onClickPresentFlutterPage(_ sender: UIButton, forEvent event: UIEvent){ FlutterBoostPlugin.present("second", urlParams:[kPageCallBackId:"MycallbackId#2"], exts: ["animated":true], onPageFinished: { (_ result:Any?) in print(String(format:"call me when page finished, and your result is:%@", result as! CVarArg)); }) { (f:Bool) in print(String(format:"page is presented")); } } } *注意点: (1).调用FlutterBoostPlugin.open和present方法时候,传递的urlname(first、second)会在Flutter中注册对应的Widget。 <2>.Flutter平台 1.main.dart (可以理解为入口类,类似iOS中的Appdelete类),实现Flutter_boost初始化和注册Native对应的widget。 (1)初始化flutter_boost插件及注册对应的widgets。 (2)初始化路由router。 import 'package:flutter/material.dart'; import 'package:flutter_boost/flutter_boost.dart'; import 'simple_page_widgets.dart'; void main() { runApp(MyApp()); } class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State { @override void initState() { super.initState(); //(1)初始化注册 FlutterBoost.singleton.registerPageBuilders({ 'first': (String pageName, Map params, String _) => FirstRouteWidget(), 'firstFirst': (String pageName, Map params, String _) => FirstFirstRouteWidget(), 'second': (String pageName, Map params, String _) => SecondRouteWidget(), 'secondStateful': (String pageName, Map params, String _) => SecondStatefulRouteWidget(), }); } @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Boost example', //(2)初始化路由 builder: FlutterBoost.init(postPush: _onRoutePushed), home: Container(color: Colors.white)); } //(2) void _onRoutePushed( String pageName, String uniqueId, Map params, Route route, Future _, ) {} } 注意点: (1)import 'simple_page_widgets.dart'这个dart类中是实现了很多widget,比如first对应的FirstRouteWidget,second对应的SecondRouteWidget等。但是在实际开发中不要把widget放到一个Dart类种实现,降低代码冗余、代码复杂度、降低维护成本等。 2.实现widget(具体可以查看官方类中的simple_page_widgets.dart) 下边只展示我们之前对应的first和second。 import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_boost/flutter_boost.dart'; import 'package:flutter_module/platform_view.dart'; //封装的view /// *********************** FirstRouteWidget*/ class FirstRouteWidget extends StatefulWidget { @override State createState() => _FirstRouteWidgetState(); } class _FirstRouteWidgetState extends State { _FirstRouteWidgetState(); // flutter 侧MethodChannel配置,channel name需要和native侧一致 // static const MethodChannel _methodChannel = MethodChannel('flutter_native_channel'); // String _systemVersion = ''; // Future _getPlatformVersion() async { // try { // final String result = await _methodChannel.invokeMethod('getPlatformVersion'); // print('getPlatformVersion:' + result); // setState(() { // _systemVersion = result; // }); // } on PlatformException catch (e) { // print(e.message); // } // } @override void initState() { print('initState'); super.initState(); } @override void didChangeDependencies() { print('didChangeDependencies'); super.didChangeDependencies(); } @override void didUpdateWidget(FirstRouteWidget oldWidget) { print('didUpdateWidget'); super.didUpdateWidget(oldWidget); } @override void deactivate() { print('deactivate'); super.deactivate(); } @override void dispose() { print('[XDEBUG] - FirstRouteWidget is disposing~'); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('First Route')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ RaisedButton( child: const Text('Open native page'), onPressed: () { print('open natve page!'); FlutterBoost.singleton .open('native') .then((Map value) { print( 'call me when page is finished. did recieve native route result $value'); }); }, ), RaisedButton( child: const Text('Open FF route'), onPressed: () { print('open FF page!'); FlutterBoost.singleton .open('firstFirst') .then((Map value) { print( 'call me when page is finished. did recieve FF route result $value'); }); }, ), RaisedButton( child: const Text('Open second route1'), onPressed: () { print('open second page!'); FlutterBoost.singleton .open('second') .then((Map value) { print( 'call me when page is finished. did recieve second route result $value'); }); }, ), RaisedButton( child: const Text('Present second stateful route'), onPressed: () { print('Present second stateful page!'); FlutterBoost.singleton.open('secondStateful', urlParams: { 'present': true }).then((Map value) { print( 'call me when page is finished. did recieve second stateful route result $value'); }); }, ), RaisedButton( child: const Text('Present second route'), onPressed: () { print('Present second page!'); FlutterBoost.singleton.open('second', urlParams: { 'present': true }).then((Map value) { print( 'call me when page is finished. did recieve second route result $value'); }); }, ), RaisedButton( child: Text('Get system version by method channel:' + _systemVersion), onPressed: () => _getPlatformVersion(), ), ], ), ), ); } } /// *********************** SecondRouteWidget*/ class SecondRouteWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Second Route')), body: Center( child: RaisedButton( onPressed: () { // Navigate back to first route when tapped. final BoostContainerSettings settings = BoostContainer.of(context).settings; FlutterBoost.singleton.close( settings.uniqueId, result: {'result': 'data from second'}, ); }, child: const Text('Go back with result!'), ), ), ); } } 注意点: (1).状态管理的处理,如果细心地同学会发现,上边FirstRouteWidget和SecondRouteWidget的继承类不同,一个是StatelessWidget(非动态的)、StatefulWidget(动态的)两种状态管理。具体理解可以自己去查资料学习,后续也会专门讲下这个。 场景: 1.原生-->>原生 这个不多说直接还是原生跳转就行 2.原生-->>Flutter 调用PlatformRouterImp中的方法 3.Flutter-->>原生 需要Flutter代码执行 FlutterBoost.singleton .open('native') .then((Map value) { print( 'call me when page is finished. did recieve native route result $value'); }); 4.Flutter-->>Flutter (走Native原生Controller容器跳转) FlutterBoost.singleton .open('firstFirst') .then((Map value) { print( 'call me when page is finished. did recieve FF route result $value'); }); 5.Flutter-->>Flutter(走Flutter自己的Flutter Navigator 跳转) Navigator.push( context, MaterialPageRoute(builder: (_) => PushWidget()), ); 这个自己去做处理,建议既然使用flutter_boost就都采用flutter_boost统一的路有跳转。 如果想实现只打开一个Flutter的原生控制器,其他走flutter内部跳转router逻辑,则需要自己处理,或者也可以看下flutter_thrio框架。 到这里基本上全部工作都可以了,Flutter和iOS都会关联上。直接运行Xcode就可以了。 关于分离开发: 需要注意的地方如果之后考虑分开开发,Flutter同学只开发Flutter的话,需要在flutter_module/.ios/下工程配置之前iOS端的东西即可,就可以使用flutter run直接跑起来,且也能使用flutter_boost路由。做到Flutter开发Flutter,原生开发原生。做到分离。 下一篇会写下Flutter_Boost在Android中的集成应用和代码实践。 萌图镇楼

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):