Kevinlma的博客

物来顺应,未来不迎,当时不杂,既过不恋

0%

一步步通过flutter_boost来集成Flutter到iOS项目

简单集成flutter到已有项目

创建Flutter Module

flutter的集成可以是创建一个 新的flutter app 的方式,也可以是以创建一个module的方式,集成到一个已有的App中。一般项目都会通过第二种方式,来逐步进行Native和Flutter代码的混编。这里也主要是采用第二种方式,来一步步演示集成步骤。

这种集成方式要求flutter侧的代码是通过module的方式来创建,所以先创建一个示例的module。运行下面创建代码

1
2
~/Desktop/demo                                                                                                                                                                                                                                                                                                       ⍉
▶ flutter create -t module -i objc flutter_module

之后会生成一个flutter_module的文件夹,里面就是我们要集成的flutter module。创建好之后, 模板已经生成了一个最简单的界面。我们先把这个module放到一边,先到App中尝试把它集成进来。

Native端集成Flutter module

创建iOS项目Demo工程

这里我们先预先创建了一个单视图的iOS Demo项目。谷歌推荐使用cocoapod来集成flutter模块。由于Demo工程如果还没有Podfile,就需要先创建一个Podfile。注意要开启use_frameworks!

1
2
3
4
5
6
source 'https://github.com/CocoaPods/Specs.git'
platform :ios,'8.0'
use_frameworks!

target 'demoApp' do
end

之后执行pod install,生成项目的workspace。

Podfile中加入对flutter module的依赖

往已有的App中集成flutter module时又有两种方式,一种是通过源码的方式集成,这种要求开发者机器上装有flutter开发环境。另一种是把flutter模块的代码打包成framework,然后添加到目的app中。为了方便演示,这里采用第一种。

首先往Podfile里增加导入flutter的代码,加之后Podfile是这个样子。

1
2
3
4
5
6
7
8
9
10
11
12
source 'https://github.com/CocoaPods/Specs.git'
platform :ios,'8.0'

#这里添加如下代码,加载flutter模板中提供的ruby代码。注意路径改成自己建的flutter_module
flutter_application_path = '../flutter_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

target 'demoApp' do

#target里增加这句调用,模板提供的方法,把flutter相关导入到已有的App项目中
install_all_flutter_pods(flutter_application_path)
end

加完后重新执行一次pod install,之后就可以看到项目的Pod工程里多了Development Pods.

其中,Flutter是引擎相关代码,flutter_module是业务相关dart,我们开发的dart代码就被编译在这个framework里。FlutterPluginRegistrant是注册flutter插件用的。

Native项目代码里使用Flutter module

首先需要创建Flutter Engine。方便起见,我们直接在AppDelegate.h中声明

1
2
3
4
5
6
7
8
9
#import <UIKit/UIKit.h>

//加入flutter framework的依赖
@import Flutter;

@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (nonatomic, strong) FlutterEngine* flutterEngine; //声明一个flutter引擎的变量

@end

在App启动后,及时初始化一下Flutter引擎。当然也可以在后续要用到Flutter界面的时候才初始化,但那可能会造成Flutter界面展现时的一点卡顿,所以可以尽早初始化。

1
2
3
4
5
6
7
8
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

//初始化Flutter引擎,并运行。
self.flutterEngine = [[FlutterEngine alloc] initWithName:@"demo"];
[self.flutterEngine run];

return YES;
}

准备好引擎之后,我们就已经可以在需要的地方展现Flutter模块界面了。Flutter的界面都是通过framework中提供的FlutterViewController来加载的。 下面通过在一个vc中加入一些测试代码,来展现刚才模板中创建的flutter界面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)viewDidLoad {
[super viewDidLoad];

// 点击后展现Flutter界面
UITapGestureRecognizer* tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap)];
[self.view addGestureRecognizer:tap];
}

- (void)onTap
{
//获取刚才创建的flutter引擎。
FlutterEngine* engine = [(AppDelegate*)[UIApplication sharedApplication].delegate flutterEngine];

//创建一个FlutterViewController,传入flutter引擎。
FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];

//展现flutter
[self.navigationController pushViewController:vc animated:YES];

//隐藏默认的导航栏。因为flutter中界面是全屏显示的
[self.navigationController setNavigationBarHidden:YES];
}

顺利的话,点击一下vc,就可以看到Flutter module中的界面展现出来了。
img

使用flutter_boost来混合开发

目前展现的是模板生成的主界面,但项目中的混编应用会远比这复杂,可能会有多个flutter界面,并且跟Native交互也更复杂。通过Boost插件来更好的实现原生和Flutter的混合开发,这里做个简单例子。

flutter_module部分

创建子页面

先打开刚才创建的flutter_module,准备一个要加载的子页面
业务开发的dart代码一般都放在lib文件下,先在lib目录下创建一个subpage.dart的文件,作为后续要加载的一个普通的子页面。简单创建一个 SubPage.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import 'package:flutter/material.dart';

class SubPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SubPage'),
),
body: Container(
color: Colors.orange,
child: Center(
child: Text('this is subpage'),
),
),
);
}
}

增加flutter_boost第三方package

打开module工程里的pubspec.yaml文件,flutter对第三方库的依赖是通过这个文件来管理的,类似于oc项目中的Podfile。我们在其中加入对flutter_boost的依赖

1
2
3
4
5
6
7
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2

#这行,版本的规则和cocoapod是基本一样的
flutter_boost: ^1.12.13

之后在terminal中当前目录下执行flutter pub get命令行。用VSCode时,每次保存这个文件时这个命令也会自动触发。

flutter_boost添加完成后,我们就可以设置对它子页面的路由管理。

module中使用flutter_boost

打开main.dart,首先增加flutter_boost的dart导入

1
import 'package:flutter_boost/flutter_boost.dart';

然后把下面_MyHomePageState类的实现代码全部删掉,这些模板创建的代码已经用不到了,改成以下这样。关键是增加通过FlutterBoost来管理页面路由的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class _MyHomePageState extends State<MyHomePage> {

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Boost Example',
builder: FlutterBoost.init(), //关键是这里
home: Container(
child: Text('test'),
color: Colors.white,
),
);
}
}

接下来再给FlutterBoost注册一下我们的子页面,这部分代码我们放在_MyHomePageState的initState()方法中去做。

1
2
3
4
5
6
7
8
9
10
11
void initState() {
super.initState();

//建立一个路由名称:builder的map。别忘了在文件头部也加上 import 'subpage.dart'; 否则SubPage类不认识
final route_builder = {
'subpage': (String name, Map<String, dynamic> param, String _) =>
SubPage()
};
//向FlutterBoost注册路由,
FlutterBoost.singleton.registerPageBuilders(route_builder);
}

做完这些后,flutter_module这部分就暂时完成了,接下来就可以去App中使用FlutterBoost接入了

Native端

Native端也增加flutter_boost的依赖

因为flutter端使用了flutter_boost插件,并且代码有了改动,所以在Terminal中重新执行一次pod install。可以看到Native这边也增加上了flutter_boost相关的pod库。

1
2
3
4
5
Analyzing dependencies
Downloading dependencies
Installing FlutterPluginRegistrant 0.0.1
Installing flutter_boost (0.0.2)
...

Native端主要可以分成两个部分:flutter_boost的初始化,以及与flutter端的交互。
我们看看与flutter端的交互部分。

添加flutter_boost对路由跳转的平台实现

flutter_boost的实现机制,个人理解大致是每个flutter中的页面都通过Native这边的一个FLBFlutterViewContainer来承载。而FLBFlutterViewContainer都共享同一个FlutterEngine。FLBFlutterViewContainer的展现和消失,不论是Native端发起还是Flutter端发起,最后都是Native端来实现的。具体可以参考flutter_boost的介绍

首先创建一个FlutterPlatform的类,实现FLBPlatform协议,来实现flutter端发起的跳转请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@import flutter_boost;
//
@interface FlutterPlatform : NSObject<FLBPlatform>
@end

@implementation FlutterPlatform
//实现push的处理
- (void)open:(NSString *)url
urlParams:(NSDictionary *)urlParams
exts:(NSDictionary *)exts
completion:(void (^)(BOOL finished))completion
{
FLBFlutterViewContainer* vc = [[FLBFlutterViewContainer alloc] init];
vc.name = url;

//这里每个项目的处理都会不一样,这里只是举个例子
UIViewController* rootViewController = [[UIApplication sharedApplication].delegate.window rootViewController];
if ([rootViewController isKindOfClass:[UINavigationController class]]) {
[(UINavigationController*)rootViewController pushViewController:vc animated:YES];
}
}

//实现close的处理
- (void)close:(NSString *)uid
result:(NSDictionary *)result
exts:(NSDictionary *)exts
completion:(void (^)(BOOL finished))completion
{
UIViewController* vc = FlutterBoostPlugin.sharedInstance.currentViewController;
if (vc.navigationController) {
[vc.navigationController popViewControllerAnimated:YES];
}else if(vc.presentingViewController){
[vc.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
}
@end

初始化flutter_boost

FLBFlutterViewContainer的协作处理类建立好之后,我们就可以去进行flutter_boost的初始化了。由于它会接管FlutterEngin的创建,所以我们之前的Engine初始化代码需要修改一下。回到AppDelegate类中,将之前的FlutterEngine初始化代码都删掉,换成flutter_boost提供的初始化方式.

1
2
3
4
FlutterPlatform* platform = [[FlutterPlatform alloc] init];
[FlutterBoostPlugin.sharedInstance startFlutterWithPlatform:platform onStart:^(FlutterEngine * _Nonnull engine) {
self.flutterEngine = engine;
}];

通过flutter_boost打开Flutter中的子页面

到这里,通过flutter_boost来接入flutter就已经基本完成了。我们可以来试着通过flutter_boost来打开我们刚才创建的subpage页面。还是在我们前面添加点击事件的地方,换成以flutter_boost的方式打开

1
2
3
4
5
6
7
8
9
10
- (void)onTap
{
[FlutterBoostPlugin open:@"subpage" urlParams:@{} exts:@{} onPageFinished:^(NSDictionary * result) {} completion:^(BOOL completion) {}];

// FlutterEngine* engine = [(AppDelegate*)[UIApplication sharedApplication].delegate flutterEngine];
// FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
// [self.navigationController pushViewController:vc animated:YES];

[self.navigationController setNavigationBarHidden:YES];
}

没意外的话,应该可以看到我们创建的子页面被展现出来了。

Flutter中调用关闭当前子页面

但是目前展现,不能回退,返回去再修改一下dart代码,在界面上增加一个返回按钮。通过flutter_boost来退出该界面了。修改subpage的代码,在Appbar上增加一个返回按钮

1
2
3
4
5
6
7
8
appBar: AppBar(
title: Text('SubPage'),
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
FlutterBoost.singleton.closeByContext(context);
}),
),

顺利的话,点击新加的返回按钮,就可以正常退出了。

最后

上面记录的是一个最基本的接入流程,方便自己后续学习。后面还有很多细节需要根据项目需要去调整,比如插件的注册,路由跳转的平台实现等等。