2020年之前有些教程创建方式已经不适用,要么缺文件、要么原生项目运行报错,折腾几天半个月都不行,还是官方文档教程比较靠谱。
目前原生App和Flutter混合开发有两种模式:
- 统一管理模式:将原生工程作为Flutter工程的子工程,由Flutter进行统一管理。
- 三端分离模式:将Flutter工程作为原生工程的子模块,维持原有的原生工程管理方式不变。
一、基于原有iOS项目集成Flutter框架
前提要拥有CocoaPods和Flutter环境配置
Flutter官方文档
1.1、创建项目文件夹
新建总项目文件夹,存放三端项目文件如:carry_sniper
1.2、创建Flutter模块
在总项目文件夹内,创建Flutter模块工程如:flutter_module
cd xxx/carry_sniper 你的文件夹路径
flutter create --template module flutter_module
执行结果:
Creating project flutter_module...
flutter_module/test/widget_test.dart (created)
flutter_module/flutter_module.iml (created)
flutter_module/.gitignore (created)
flutter_module/.metadata (created)
flutter_module/pubspec.yaml (created)
flutter_module/README.md (created)
flutter_module/lib/main.dart (created)
flutter_module/flutter_module_android.iml (created)
flutter_module/.idea/libraries/Dart_SDK.xml (created)
flutter_module/.idea/modules.xml (created)
flutter_module/.idea/workspace.xml (created)
Running "flutter pub get" in flutter_module... 0.8s
Wrote 11 files.
All done!
Your module code is in flutter_module/lib/main.dart.
1.3、创建iOS项目工程
在总项目文件夹,使用Xcode创建iOS项目工程如:CarrySniperiOS
1.4、关联操作
1.4.1为iOS工程添加CocoaPods依赖,生成Proflie文件
终端指令执行:
cd xxx/carry_sniper/CarrySniperiOS 你的iOS工程项目路径
pod init
1.4.2打开Proflie文件添加、修改内容
主要2段3行代码,注意代码存放位置,和flutter_module
名称一致
flutter_application_path = '../flutter_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
install_all_flutter_pods(flutter_application_path)
配置结果如下:
platform :ios, '11.0'
# 1、Flutter模块加入
flutter_application_path = '../flutter_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'CarrySniperiOS' do
use_frameworks!
# 2、安装嵌入Flutter模块
install_all_flutter_pods(flutter_application_path)
# Pods for CarrySniperiOS
end
1.4.3执行指令,完成Flutter模块的添加和CocoaPods依赖
pod install
执行结果:
要看到Installing Flutter相关依赖的安装,否则运行报错
关闭当前Xcode项目,从此使用CarrySniperiOS.xcworkspac运行工程
/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/universal-darwin19/rbconfig.rb:229: warning: Insecure world writable dir /Users/Macbook/Documents/FlutterSDK/flutter/bin in PATH, mode 040777
Analyzing dependencies
Downloading dependencies
Installing Flutter (1.0.0)
Installing FlutterPluginRegistrant (0.0.1)
Installing flutter_module (0.0.1)
Generating Pods project
Integrating client project
[!] Please close any current Xcode sessions and use `CarrySniperiOS.xcworkspace` for this project from now on.
Pod installation complete! There are 3 dependencies from the Podfile and 3 total pods installed.
1.5、完成基础配置
可以直接打开项目运行,没问题就说明配置成功。部分目录如下:
carry_sniper/
├── CarrySniperiOS/
│ ├── CarrySniperiOS/
│ ├── Pods/
│ ├── Podfile
│ ├── Podfile.lock
│ ├── CarrySniperiOS.xcodeproj
│ └── CarrySniperiOS.xcworkspace
├── flutter_module/
│ ├── .android/
│ ├── .ios/
│ │ ├── Runner.xcworkspace
│ │ └── Flutter/podhelper.rb
│ ├── lib/
│ └── main.dart
│ ├── flutter_module_android.iml
│ ├── flutter_module.iml
│ ├── pubspec.lock
│ ├── test/
│ └── pubspec.yaml
二、原生iOS项目调用Flutter
根据官方Flutter文档,进行简单的原生app调用Flutter,列举2种方法,这里是Objective-C代码,文档有Swift代码。更高级的调用方法可以继续看文档。
2.1方式一:使用FlutterViewController
直接在ViewController.m文件编写代码,运行项目即可:
#import "ViewController.h"
#import <Flutter/Flutter.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self action:@selector(showFlutter)
forControlEvents:UIControlEventTouchUpInside];
[button setTitle:@"Show Flutter!" forState:UIControlStateNormal];
button.backgroundColor = UIColor.blueColor;
button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
[self.view addSubview:button];
}
- (void)showFlutter {
FlutterViewController *flutterViewController = [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil];
[self presentViewController:flutterViewController animated:YES completion:nil];
}
@end
2.2、方式二:使用FlutterEngine
2.1.1、依赖FlutterAppDelegate创建一个实体FlutterEngine
AppDelegate.h文件内容:
#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>
@interface AppDelegate : FlutterAppDelegate
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end
AppDelegate.m文件内容:
#import "AppDelegate.h"
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
// Runs the default Dart entrypoint with a default Flutter route.
[self.flutterEngine run];
// Used to connect plugins (only if you have plugins with iOS platform code).
[GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
2.2.2、使用FlutterEngine调用Flutter页面和传参
ViewController.m文件内容:
#import "ViewController.h"
#import "AppDelegate.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// Make a button to call the showFlutter function when pressed.
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self action:@selector(showFlutter)
forControlEvents:UIControlEventTouchUpInside];
[button setTitle:@"Show Flutter!" forState:UIControlStateNormal];
button.backgroundColor = UIColor.blueColor;
button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
[self.view addSubview:button];
}
- (void)showFlutter {
FlutterEngine *flutterEngine =
((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;
FlutterViewController *flutterViewController =
[[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
[self presentViewController:flutterViewController animated:YES completion:nil];
}
@end
2.2.3、运行Xcode中的CarrySniperiOS项目
三、原生iOS项目也能使用热更新、热重载
官方文档:
要先安装homebrew,运行Xcode项目,保证原生App有安装到手机/模拟器上,然后在Android Studio的Flutter项目跟路径执行指令:
flutter attach
如果有多个设备(或者输入下标选择相应设备):
flutter attach -d xxxxx设备id
如果有多个包名:
那么需要统一Android和iOS的包名后再试一遍
如果出现Waiting for a connection from Flutter on iPhone ...
一直等待,
说明原生App没有启动,控制台连接不到设备应用运行。
需要手动到手机/模拟器点击运行App即可(只需运行Android Studio,不需要打开Xcode软件,Xcode会在Flutter下默默运行。当然不打开Xcode,就看不到控制台打印输出,但影响不大)。
执行结果:
Waiting for iPhone 12 Pro to report its views... 7ms
Syncing files to device iPhone 12 Pro...
8,140ms (!)
Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h Repeat this help message.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).
An Observatory debugger and profiler on iPhone 12 Pro is available at: http://127.0.0.1:51810/ucfjPzF2_sY=/
四、原生iOS项目后续可能遇到的问题
如果发现运行App进度和Flutter开发进度不一致,需要去Xcode运行原生项目App。需要保证打包的代码是最新的,否则安装的App再次启动后,永远是之前的版本,没有包含最新Flutter部分的代码。
个人理解:已安装的App,我们使用饭flutter attach,热更新和热重载只保证运行时代码是最新的,运行结束之后就恢复原生App安装时的模样,并没有把最新的Flutter代码打包到安装包里面。
Showing Recent Messages Undefined symbol: protocol conformance descriptor fo xxx 等几十上百个报错
莫名其妙的出现,之前运行还好好的,可能Flutter添加了某些package,在原生项目就突然出问题了。
可能解决方法:
尝试一:升级CocoaPods;
尝试二:更新依赖库 pod install --verbose --no-repo-update
尝试三:为原生项目添加swift桥接文件,任意直接New一个.swift文件,Xcode 提示 Create Bridging Header ,选择创建即可。记得.swift文件保留不删除。
Command PhaseScriptExecution failed with a nonzero exit code
/packages/flutter_tools/bin/xcode_backend.sh: No such file or directory
先检查Flutter SDK里面存不存在xcode_backend.sh文件,不存在就去找一个或者重新下载sdk;
存在的话,可能就是Xcode项目配置可能缺少FLUTTER_ROOT,Target -> Build Setting -> User-Defined 添加FLUTTER_ROOT对应sdk路径;
如果不知道路径可以直接复制flutter项目的.ios的Generated.xcconfig里面的FLUTTER_ROOT内容,会自动帮导入到User-Defined。
Support for empty structs is deprecated and will be removed in the next stable version of Dart. Use Opaque instead.
遇到SDK版本更新,API过期更替。不影响使用,需要等待第三方插件更新支持最新包。可以用'flutter downgrade'指令降级SDK。
五、一些想法
三端分离,flutter里面iOS的info.plist文件很容易在安卓合并代码或pub get指令执行时会被重置,影响开发和调试。当然原生项目的info.plist文件是不影响的,只是不想开发期间直接用原生项目,效率不一样。