最近由于公司产品进行优化,需要将之前RN项目中部分相对独立并且消耗性能的模块重写。经过讨论,最后选择了flutter重写该模块进行尝试,以便于后期使用flutter进一步开发公司项目。
当前的demo版本 flutter:^2.0.5,react-native:0.64.0,Xcode:12.5
混编方案
混编方案可参考 flutter官网文档,官网一共提到了三种集成方案:
- 使用 CocoaPods 和 Flutter SDK 集成
- 在 Xcode 中集成 frameworks
- 使用 CocoaPods 在 Xcode 和 Flutter 框架中内嵌应用和插件框架
我选择了第一种集成方案,主要在于第一种更适合项目中模块化集成和编辑调试。以下我将步骤详细列出:
(该文适合已经开发过rn和ios的开发者,所以rn和ios开发等相关环境搭建和工具的集成就不在此处说明)
步骤一:创建项目
1.创建rn项目: 由于公司的rn项目是使用ts编写的,所以我的demo也采用ts模板创建
npx react-native init RNAndFlutter --template react-native-template-typescript
2.创建flutter module
flutter create --template module my_flutter
步骤二:将flutter module集成到react-native项目中
按照官方文档给的例子是将ios项目和flutter module放在同级目录,如下所示:
然后在Podfile中添加
flutter_application_path = '../my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'MyApp' do
install_all_flutter_pods(flutter_application_path)
end
用编辑器把创建的flutter module(my_flutter是我创建的flutter module,后面都以这个名字来代替)打开,然后在目录下执行flutter pub get,该步骤会给my_flutter生成一个完整的可运行的flutter项目,执行完之后再执行pod install,刚才生成的flutter项目中的podhelper.rb 脚本会把你的 plugins, Flutter.framework,和 App.framework 集成到ios项目中。执行完上述步骤之后,flutter就已经初步与rn工程混编在一起了。
由于该模块在公司的其他多个项目中也会用到,采用上面说的flutter module直接与项目关联会稍有不妥,之后的维护成本和难度会增加。所以我们的集成方式稍有不同,我们把生成的flutter module上传到公司的gitLab,然后在rn项目中添加node modules的方式添加进去(注意:因为flutter的项目实际挺大的,在.gitignore配置,不要把.ios和.android文件夹一并传,这些都可以在项目中运行生成)。同时,如果采用此方案,那上述的Podfile添加也需要稍作修改,例如:flutter_application_path = '../node_modules/my_flutter/my_flutter',执行这个之前切换到my_flutter目录下,执行flutter pub get
做完上述步骤我执行react-native run-ios,发现ios项目编译报错,Flipper-Folly版本有问题,试了其他解决方案,列如use_flipper!修改为use_flipper!({ 'Flipper-Folly' => '2.3.0' }),还是会有其他问题
错误:Typedef redefinition with different types ('uint8_t' (aka 'unsigned char') vs 'enum clockid_t')
解决方案:直接注销Flipper,然后删掉pod的相关文件,重新执行pod install。解决方案链接
步骤三:配置ios项目
下述步骤可参照官网来做,毕竟官网肯定比我说的详细,我简述一下
- 删掉ios项目中原来的plist,然后新建release和debug的plist文件
- 在debug.plist文件中添加
<key>NSBonjourServices</key>
<array>
<string>_dartobservatory._tcp</string>
</array>
<key>NSLocalNetworkUsageDescription</key>
<string>flutter will use your local network</string>
- 在build settings中修改info.plist的文件路劲
- Build Settings > Build Phases > Copy Bundle如果有Info-Release.plist,则删掉
- Excluded Architectures debug中添加arm64
步骤四:RN或者Native跳转Flutter
关于导航跳转,官网给了比较详细的说明,照着做就行。但是,估计是文档没有更新,第二种方案中遵循FlutterPluginAppLifeCycleDelegate的[self.rootFlutterViewController handleStatusBarTouches:event];这一句会报错,我目前是是直接注释掉,运行似乎不受影响。(如果有更好的解决方案请留言或者私信告诉我,感激不尽)
由于项目之后可能会涉及更多混合跳转的问题,我目前就试试水,采用的阿里闲鱼的flutterBoost框架。接下来我简介一下集成flutterBoost
- 在pubspec.yaml添加并执行 flutter pub get和pod install
flutter_boost:
git:
url: 'https://github.com/alibaba/flutter_boost.git'
ref: 'v3.0-beta.6'
-
参照flutterBoost的说明,创建MyFlutterBoostDelegate文件,代码直接完全copy下来就行。建议直接下载demo然后把文件拖进去,简单快捷不出错。
-
在Appdelegate中添加
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#ifdef FB_SONARKIT_ENABLED
InitializeFlipper(application);
#endif
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"RNAndFlutter"
initialProperties:nil];
if (@available(iOS 13.0, *)) {
rootView.backgroundColor = [UIColor systemBackgroundColor];
} else {
rootView.backgroundColor = [UIColor whiteColor];
}
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
UINavigationController * nav = [[UINavigationController alloc]initWithRootViewController:rootViewController];
nav.navigationBarHidden = YES;
rootViewController.view = rootView;
self.window.rootViewController = nav;
[self.window makeKeyAndVisible];
//添加flutterboost相关的
MyFlutterBoostDelegate* delegate=[[MyFlutterBoostDelegate alloc ] init];
delegate.navigationController = nav;
[[FlutterBoost instance] setup:application delegate:delegate callback:^(FlutterEngine *engine) {
} ];
return YES;
}
- 创建RN和Native的桥接文件
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTLog.h>
NS_ASSUME_NONNULL_BEGIN
@interface RNAndNativeHelper : NSObject<RCTBridgeModule>
@end
NS_ASSUME_NONNULL_END
#import "RNAndNativeHelper.h"
#import <flutter_boost/FlutterBoost.h>
@implementation RNAndNativeHelper
RCT_EXPORT_MODULE();
/** RN跳转flutter
*@param 界面跳转传参
*/
RCT_EXPORT_METHOD(pushToFlutter:(NSString *)param)
{
[[FlutterBoost instance] open:@"/" arguments:@{@"animated":@(YES),@"params":param}];
}
@end
- 配置flutter的路由
import 'package:flutter/material.dart';
import 'package:flutter_boost/flutter_boost_app.dart';
import 'HomePage.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
static Map<String, FlutterBoostRouteFactory>
routerMap = {
'/': (settings, uniqueId) {
//跳转传参解析
var paramTmp = settings.arguments;
Map<String,dynamic> arguments = new Map<String,dynamic>.from(paramTmp);
String paramTmpStr = arguments['params'];
Map<String,dynamic> parmas = convert.jsonDecode(paramTmpStr);
return PageRouteBuilder<dynamic>(
settings: settings, pageBuilder: (_, __, ___)
=> MyHomePage(title: parmas['title'] ?? '...',));
},
'embedded': (settings, uniqueId) {
return PageRouteBuilder<dynamic>(
settings: settings,
pageBuilder: (_, __, ___) =>
MyHomePage());
}};
Route<dynamic> routeFactory(RouteSettings settings, String uniqueId) {
FlutterBoostRouteFactory func =routerMap[settings.name];
if (func == null) {
return null;
}
return func(settings, uniqueId);
}
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return FlutterBoostApp(
routeFactory
);
}
}
- 由RN跳转到flutter,然后运行
const RNAndNativeHelper = NativeModules.RNAndNativeHelper;
const params = {title:'React-Native'}
RNAndNativeHelper.pushToFlutter(JSON.stringify(params));
运行起来,此时出现闪退,可删除plist中的NSAppTransportSecurity下面的NSExceptionDomains,再次运行就不会闪退了。如果出现只能用Xcode运行才能加载flutter代码,则运行flutter attach来连接设备再用其他编译器运行。
到此,整个集成过程就全部介绍完了,如果有啥问题留言,通常第二天会回复。
demo在这里,有需要的可以下载运行
参考链接: