Flutter-boot 你集成成功了吗?

flutter-boot

首先区分两个概念'flutter-boot''flutter_boost'都是alibba开源的flutter工具,可是是两个彻底不一样的东西。node

FlutterBoost

新一代Flutter-Native混合解决方案。 FlutterBoost是一个Flutter插件,它能够轻松地为现有原生应用程序提供Flutter混合集成方案。FlutterBoost的理念是将Flutter像Webview那样来使用。在现有应用程序中同时管理Native页面和Flutter页面并不是易事。 FlutterBoost帮你处理页面的映射和跳转,你只需关心页面的名字和参数便可(一般能够是URL)。android

Flutter-boot

这是一个帮助你在已有原生应用的状况下,搭建flutter混合开发环境的工具。 咱们提供了标准的混合工程结构,同时支持混合栈(一套原生和flutter以前页面通讯和过渡的方案)的快速接入。ios

以上是摘抄GitHub上flutter-boot 和 flutter_boost的官方介绍,也由于官方文档上的集成步骤太过简略,在集成的过程当中才过了很多的坑,因此都记录下来而且分享出来,但愿能够给看文章的你,带去一些解决方案和思路。git

本文主要记录一次flutter-boot的集成过程,对于flutter_boost的使用放在后续的文章中再介绍。github

Native工程环境

已有一个维护迭代过多个版本的Native工程,而且已经作过组件化,拆分了多个组件(就是常见的组合组件的方法,经过CocoaPods的方式添加安装各个组件,制做CocoaPods远程私有库,将其发不到公司的gitlab或GitHub,使工程可以Pod下载下来),本次集成flutter-boot的目的是要把其中一个组件,使用flutter重写。shell

这里强调作过组件化,是由于不少问题都是由于组件化了才引发的若是只是一个单纯的工程集成的话,会简单很多,并且后续若是只是某个组件须要依赖flutter,还须要有一些特殊的操做。npm

Flutter-boot集成步骤和遇到的问题

这里能够跟着我一块儿来操做,下面的步骤我都写得尽可能详细,遇到的问题都有截图或者文字描述,以及解决方案跟在后面。ruby

  1. $ cd somepath/my_repo

这里能够是你的Native路径 , 也能够是一个新建立的文件夹,这里我是用了一个新的文件夹路径,目的是保证以后的路径和官方文档上的目录结构一致。bash

somepath/my_repo
└──my_android
└──my_ios
└──my_flutter
    └──.git
    └──.gitignore
    └──android_shell
    └──ios_shell
    └──android
    └──ios
复制代码
  1. $ flutter-boot init

问题一:link 失败,这里忘记截图了。。。特别抱歉app

解决方案:

  • 出现这个问题的缘由,是由于flutter-boot的版本支持问题,官方文档上还要求装1.5.0的版本(其实已经支持到1.9了),可是通常咱们电脑上的版本都比这个高,因此,要先下降版本,切换到master分支,而后执行init命令
拥有^1.5.0的flutter环境
复制代码
  • 找到npm包中flutter-boot的源码,修改一下判断版本号的代码,不过。。。不推荐这种方式(可是亲测有效,毕竟来回切换版本号太浪费时间了)
  1. $ 请输入flutter工程名称: my_flutter_module (能够回车跳过,会自动生成一个叫my_flutter_module的module)
  2. $ 请输入flutter仓库地址,回车跳过
  3. $ ? 是否存在iOS工程? Yes
  4. $ ? iOS工程本地地址 somepath/my_repo/my_ios 这里输入你的native工程目录

问题二:podfile内容copy出错,你也可能不会遇到这个问题,这里是flutter-boot自己的bug,只去匹配了字符串'end',恰好咱们使用了这个日历的库,就到这里把podfile文件中的内容给截断了

pod 'FSCalendar', '2.8.0' # 日历
复制代码

解决方案:

  • 在执行flutter-boot init 以前检查一下你的podfile文件,看是否也存在这种字符中含有'end',能够先删除掉,init命令执行完以后再手动添加,注意原有target和新生成的Runner target中都要添加
  • 查看从那里开始copy截断,手动把后面的补齐
  • 注意 只copy pod 引用 不须要copy post_install中的内容(若是你的podfile中有post_install的话)
post_install do |installer_representation|
    installer_representation.pods_project.targets.each do |target|
        target.build_configurations.each do |config|
            ...
        end
    end
end
复制代码

问题三:

这里是你工程的post_install和init命令生成的fbpodhelper.rb中的post_install冲突致使,

解决方案:

  • 仍是由于下降了flutter版本致使的,使用flutter 1.9版本就不会有这个问题,可是1.5会报这个错误
  • 还有同事反馈,pod版本升级到最新也不会有这个问题,个人是1.6.1,目前最新已是1.8以上了,我尚未试这个-。-
  • 修改fbpodhelper.rb中以下代码:
flutter_application_path = fbFlutterPath
require File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
复制代码

改成:

# ============= Flutter config begin ============== #
  
  flutter_application_path = './fpf_flutter/'
  podhelper_path = File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
  
  podhelper_content = File.read(podhelper_path);
  podhelper_post_isntall = "post_install do |installer|";
  # 当post_install重复时须要去重以免发生pod install错误:multiple post_install hooks is unsupported
  if podhelper_content.scan(/(#{podhelper_post_isntall})/).length > 0 then
    
    podhelper_buffer = podhelper_content.gsub(podhelper_post_isntall, "def update_configs(installer, framework_dir)")
    eval(podhelper_buffer, binding)
    
    else
    eval(File.read(podhelper_path), binding)
  end
  
  # ============= Flutter config end ============== #
复制代码
  1. 混合工程初始化完成
info [init] 你能够在建立Android工程后调用 flutter-boot link来关联flutter
info [init] 混合工程初始化完成
复制代码
  1. 运行flutter

你觉得到这里就真的完成了吗,run一下~~ 记得要选择 Runner target,好像确实能够成功。可是flutter-boot的做用是你能够有两个开发视角,flutter视角下,不须要关心native,native视角,甚至能够不用安装flutter环境。那么,去flutter视角看一下,把flutter module拖入VSCode打开,而后fn+f5,运行起来

那么在flutter视角运行起来了吗

问题四: Run Flutter Build Script 中的脚本./my_flutter_module/.ios/Flutter/flutter_export_environment.sh 路径找不到,一个很奇怪的现象,pod install成功后查看路径,没问题,可是flutter run以后,这里的路径就改变了,相对路径错误,确实找不到这个脚本

解决方案:

  • $ cd ./my_flutter_module/.ios/Flutter/podhelper.rb
  • 打开这个文件,其实就是一个ruby脚本,找到设置flutter_export_environment.sh路径的代码,作一下修改,改成:
flutter_export_environment_path = File.join(project_directory_pathname, relative, 'flutter_export_environment.sh');
复制代码
  1. AppDelegate 中注册flutter 引擎

In AppDelegate.h:

@import UIKit;
@import Flutter;

@interface AppDelegate : FlutterAppDelegate
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end
复制代码

In AppDelegate.m:

@import FlutterPluginRegistrant; // Only if you have Flutter Plugins

#import "AppDelegate.h"

@implementation AppDelegate

// This override can be omitted if you do not have any Flutter Plugins.
- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  self.flutterEngine = [[FlutterEngine alloc] initWithName:@"io.flutter" project:nil];
  [self.flutterEngine runWithEntrypoint:nil];
  [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end
复制代码

In ViewController.m

#import "AppDelegate.h"
#import "ViewController.h"
@import Flutter;

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button addTarget:self
               action:@selector(handleButtonAction)
     forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"Press me" forState:UIControlStateNormal];
    [button setBackgroundColor:[UIColor blueColor]];
    button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
    [self.view addSubview:button];
}

- (void)handleButtonAction {
    FlutterEngine *flutterEngine = [(AppDelegate *)[[UIApplication sharedApplication] delegate] flutterEngine];
    FlutterViewController *flutterViewController = [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
    [self presentViewController:flutterViewController animated:false completion:nil];
}
@end
复制代码

至此,flutter-boot 环境集成成功!!! 喜大普奔啊,终于不报错了~~

可是,回到咱们最初集成flutter-boot的目的,是要使用flutter重写某一个业务线。由于是组件化的工程,因此flutter引擎的注册也不但愿暴露在APPdelegate中,而是放在一个管理各个业务线分发的平台组件中,那么就涉及到,某一个组件中要使用flutter。

用呗,主工程都能用,组件同样能用,而后,当你在组件代码中添加以下代码以后:

#import <Flutter/Flutter.h>
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>
复制代码

找不着,各类找不着啊。。。好像各个业务线组件根本就不能引用flutter以及flutter_module

解决方案:

  • 各业务线的podspec文件中添加依赖:
  • $ s.dependency 'Flutter'
  • $ s.dependency 'FlutterPluginRegistrant'

到这里集成flutter-boot 才算取得了阶段性的胜利✌️,但愿这篇文章,能够给广大敢于尝试flutter-boot的开发人员,带去一些福利(主要是节省一些时间,都不是什么难解决的问题,就是得不断的尝试)。

在这个过程当中,也去看了GitHub上他们全部开放和关闭的issues,而且尝试了N次,过程比较坎坷,不过成功了仍是很可的事😃。

2019.12.11 补充 flutter-boot 推到远端以后,其余同事拉代码,装flutter-boot以后遇到的一些问题记录:

问题一:

产生缘由: 曾用 root 用户进行了局部安装npm包,留下所属权为 root 的文件,致使普通用户 没法访问 root的文件内容。

解决方案:

  • 找到报无权限文件夹:/usr/ local/lib/node_ modules/ flutter -boot/node_ modules/core-js
  • 查看无权限文件夹的权限:ls -la /usr/ local/lib/node_ modules/ flutter -boot/node_ modules/core-js
    发现权限拥有者是root,但应该是咱们本机用户
  • 更改权限拥有者(后面是用户名和文件夹名): sudo chown -R 用户名 /usr/ local/lib/node_ modules/ flutter -boot/node_ modules/core-js
  • 再次查看文件夹权限就改为用户了,npm包就能够正常下载啦。

问题二:my_flutter_module中数据缺失

就是别的同事拉下flutter_module中的代码里面,没有engine这个文件夹

解决方案:

  • .gitignore中去掉对这个文件夹的忽略,而后再push一次

问题三: native视角如何使用

其实这个也不能算是个问题啦,应该归到上面的使用步骤里面,毕竟这个flutter-boot的环境搭建起来,是要给你们一块儿用的,又不是指给本身用😀

其实切换到native视角,就会以为这个工具,和我最开始选择用flutter-boot的初衷有一些偏离。最初,咱们是但愿有一套彻底不影响原生开发的框架,去接入flutter,彻底不影响是什么意思呢,领导但愿,native开发,不须要安装flutter环境,不须要,不须要,不须要,重要的地方强调一下,可是经过这几天对flutter-boot的研究,发现根本作不到这一点。

首先别人拉下native代码和flutter代码以后,须要执行link命令去作软链接,可是自己flutter-boot就是依赖flutter库的,可能说依赖不太准确,可是link命令中就有判断flutter版本的逻辑,若是没有flutter的开发环境,这一步就过不去,那你的native和flutter如何连接上呢??

解决方案:

  • 这个问题其实没有解决,只是有一个思路
  • flutter的全部产物,都是一个framework ,咱们能够手动把这些framework拖入到工程中,不用pod引入
  • 其实大部分依赖的framework 都只须要拖进去一次(这里不考虑更新)
  • 只有App.framework 须要频繁的修改,这里能够本身进入一些脚本,重定向他的产物
  • native工程中以前的依赖和各类path 都须要修改
  • 这个方案,理论上可行,可是须要改动的地方太大,还有各类维护的问题,成本都很大

若是看这篇文章的小伙伴,有更好的方式,欢迎交流!!!