# flutter_boost_example **Repository Path**: FlutterProjects/flutter_boost_example ## Basic Information - **Project Name**: flutter_boost_example - **Description**: Flutter:通过 Flutter Boost 实现 Flutter 页面与原生页面之间的跳转 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-04-17 - **Last Updated**: 2023-04-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Flutter:通过 Flutter Boost 实现 Flutter 页面与原生页面之间的跳转 ## 效果如图 ![Untitled](assets/Untitled.gif) *** # 目录 * 一、FlutterBoost 的介绍 * 二、FlutterBoost 工程到目录结构 * 三、FlutterBoost 工程中 Dart 部分的配置代码 * 四、FlutterBoost 工程中 iOS 部分的配置代码 * 五、FlutterBoost 工程中 Android 部分的配置代码 * 六、问题 ## 一、FlutterBoost 的介绍 在 `原生页面` 和 `Flutter` 页面共存的情况下,如何管理路由? 官方没有提供这样的解决方案,`FlutterBoost` 就是为了解决这个问题而生。[git地址](https://github.com/alibaba/flutter_boost) ## 二、FlutterBoost 工程到目录结构 在 `flutter_boost_example` 目录中,用 `Xcode` 创建 一个 `BoostTestIOS` 项目, 用 `Android Studio` 创建一个 `BoostTestAndroid` 项目。 然后在用 `Android Studio` 创建一个 `flutter module` 项目 `flutter_module`, 判断是不是 `module` 的方法就是看其是否有 `android` 和 `iOS` 文件夹, 如果没有,那就是 `module` **目录如图** ![目录](assets/1111.png) ## 三、FlutterBoost 工程中 Dart 部分的配置代码 ### 1、配置依赖库文件 **在 yaml 文件添加** ``` flutter_boost: git: url: 'https://github.com/alibaba/flutter_boost' ref: '4.2.0' ``` **如图所示** ![2222](assets/2222.png) ### 2、创建自定义的 Binding 接管 Flutter App 的生命周期 ``` class CustomFlutterBinding extends WidgetsFlutterBinding with BoostFlutterBinding {} ``` 在 `main.dart` 文件中的 `main` 入口方法中需要创建 `CustomFlutterBinding` 对象。这里的 `CustomFlutterBinding` 调用务必不可缺少,用于控制 `Boost` 状态的 `resume` 和 `pause`。 ``` void main() { CustomFlutterBinding(); runApp(const MyApp()); } ``` **如图所示** ![3333](assets/3333.png) 创建一个 `StatefulWidget` 模板 ![4444](assets/4444.png) ### 3、routerMap 变量的作用 在 `_MyAppState` 类中声明一个 `routerMap` 变量,对每一个页面的路由方式进行配置。 如果想用类似`iOS`平台的跳转动画,那么只需要像下面这样写成 `CupertinoPageRoute` 即可。 如果需要 push 的时候,两个页面都需要动的话,就是像 `iOS` 那样,在 `push` 的时候,前面一个页面也会向左推一段距离。 那么前后两个页面都必须是遵循 `CupertinoRouteTransitionMixin` 的路由。简单来说,就两个页面都是 `CupertinoPageRoute` 就好, 如果用 `MaterialPageRoute` 的话同理, `MaterialPageRoute` 是类似安卓平台从下往上滑出的跳转动画 ``` Map routerMap = { 'mainPage': (settings, uniqueId) { return CupertinoPageRoute( settings: settings, builder: (_) { Map? map = settings.arguments as Map; String? data = map['data']; return MainPage( data: data, ); } ); }, 'simplePage': (settings, uniqueId) { return CupertinoPageRoute( settings: settings, builder: (_) { Map? map = settings.arguments as Map; String? data = map['data']; return SimplePage( data: data, ); } ); }, }; ``` **如图** ![5555](assets/5555.png) 这里的 `routerMap` 中的 `String` 是页面的名称,而 `FlutterBoostRouteFactory` 是一个方法的重命名。这个方法的传入参数是路由的设置和 `uniqueId`,返回值是路有 `Route` 类。 **源码** ``` typedef FlutterBoostRouteFactory = Route? Function( RouteSettings settings, String? uniqueId); ``` 在 `routerMap` 中我们看到的 `mainPage` 字符串右边的模块其实就是该方法的具体实现,包括参数、函数体,返回的是一个路由 `CupertinoPageRoute` ,其为 `Route` 的子类。 `uniqueId` 其实在这里并不会使用到,我们真正关心的是 `settings` 中的参数 `arguments` ,其也为一个 `Map` 类型。我们可以从中获取要打开的新页面所必需要使用的一些参数值,并传入。 以 `mainPage` 为例,如下所示,其中`MainPage`页面是我们自定义的一个页面。 ### 4、routeFactory 方法的作用 要在 `build` 方法中创建 `FlutterBoostApp` 这样一个 `Widget` ,那么就需要传入一个 `FlutterBoostRouteFactory` 类型的参数。 ``` @override Widget build(BuildContext context) { return FlutterBoostApp( routeFactory, appBuilder: appBuilder, ); } ``` 因为其初始化方法中需要这样一个入参,而 `FlutterBoostRouteFactory` 类型正是刚才我们所说的重命名的函数类型,其可以通过在我们刚才生成的 `routerMap` 中通过页面名称进行获取。 **源码如图**![6666](assets/6666.png) 我们实现一个 `routeFactory 方法`,该方法的参数和返回值均同 `FlutterBoostRouteFactory` 类型相同,所以可以直接当作参数值传入到 `FlutterBoostApp` 的初始化方法中。 `routeFactory` 方法用于通过传入的页面名称从 `routerMap` 获取到对应的路由配置方法,并传入所需参数进行调用。 ``` Route? routeFactory(RouteSettings settings, String? uniqueId) { FlutterBoostRouteFactory? func = routerMap[settings.name!]; if (func == null) { return null; } return func(settings, uniqueId); } ``` ### 5、appBuilder 方法的作用 构建 `FlutterBoostApp` 还需要 `FlutterBoostAppBuilder` 类型的参数,其也是方法的别名。 **源码** ``` typedef FlutterBoostAppBuilder = Widget Function(Widget home); ``` 所以这里也实现一个 `appBuilder` 方法,参数和返回值同以上类型保持相同,可以直接将此方法当参数传入使用。 `appBuilde` 方法构建了一个 `MaterialApp` 类型的 `Widget` ,注意这里必须加上 `builder` 参数,否则 `showDialog` 等会出问题。 ``` Widget appBuilder(Widget home) { return MaterialApp( home: home, debugShowCheckedModeBanner: true, builder: (_, __) { return home; }, ); } ``` 在重写的 `build` 方法中,构建 `FlutterBoostApp` ,将 `routeFactory` 和 `appBuilder` 这两个方法作为参数传入。 ``` @override Widget build(BuildContext context) { return FlutterBoostApp( routeFactory, appBuilder: appBuilder, ); } ``` **完整如图** ![7777](assets/7777.png) ### 6、创建自定义的 Flutter 页面 接着就是绘制我们的UI部分了。下面的代码创建了一个 `SimplePage` 页面。 ``` class SimplePage extends StatelessWidget { const SimplePage({Object? data}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( onPressed: () { BoostNavigator.instance.pop(); }, icon: Icon(Icons.arrow_back_ios_new), ), title: const Text("商品详情 - Flutter页面"), ), body: Center( child: Text('寒蝉凄切,对长亭晚,骤雨初歇。都门帐饮无绪,留恋处,兰舟催发。执手相看泪眼,竟无语凝噎。'), ), ); } } ``` **如图** ![8888](assets/8888.png) 创建一个新的 `main_page.dart` 文件。建立一个命名为 `MainPage` 的 `Widget` 。 ``` class MainPage extends StatefulWidget { final String? data; const MainPage({Key? key, this.data}): super(key: key); @override State createState() => _MainPageState(); } class _MainPageState extends State { @override void initState() { super.initState(); print("进入到 Flutter 的商品列表页面"); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( onPressed: () { BoostNavigator.instance.pop(); }, icon: Icon(Icons.arrow_back_ios_new), ), title: const Text("主页面 - Flutter页面"), ), body: Container( color: Colors.grey.withOpacity(0.1), width: double.infinity, height: double.infinity, child: Center( child: InkWell( child: Text(widget.data ?? ""), onTap: () { // Map? arguments = new Map(); // arguments['content'] = '这是从Flutter主页面传过来的数据'; // BoostNavigator.instance.push( // "about", // arguments: arguments // ); }, ), ), ), floatingActionButton: FloatingActionButton.extended( label: Text( "跳转到原生页面", style: TextStyle( color: Colors.black ), ), backgroundColor: Colors.white, onPressed: () { Map? arguments = new Map(); arguments['content'] = '这是从Flutter主页面传过来的数据'; BoostNavigator.instance.push( "about", arguments: arguments ); }, ), ); } } ``` **如图** ![9999](assets/9999.png) ## 四、FlutterBoost 工程中 iOS 部分的配置代码 ### 1、配置 Podfile 文件 在 `Podfile` 文件, 添加 ``` flutter_application_path = '../flutter_module' load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb') install_all_flutter_pods(flutter_application_path) ``` **完整如下** ``` source 'https://github.com/CocoaPods/Specs.git' platform :ios, '11.0' use_frameworks! flutter_application_path = '../flutter_module' load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb') target 'BoostTestIOS' do use_frameworks! install_all_flutter_pods(flutter_application_path) # 其他的pod库 pod 'SnapKit' end post_install do |installer| flutter_post_install(installer) if defined?(flutter_post_install) end ``` ### 2、实现 FlutterBoostDelegate 委托方法 新建一个 `BoostDelegate` 文件 ``` import Foundation import flutter_boost class BoostDelegate: NSObject { /// 单例 public static let shared = BoostDelegate() ``` `FlutterBoostDelegate` 委托包括三个方法必须实现的方法: - [x] pushNativeRoute:如果框架发现您输入的路由表在 flutter 里面注册的路由表中找不到,那么就会调用此方法来 push一个纯原生页面。 - [x] pushFlutterRoute:当框架的 withContainer 为 true 的时候,会调用此方法来做原生的 push - [x] popRoute:当 pop 调用涉及到原生容器的时候,此方法将会被调用 ### 3、从 Flutter 页面跳转到 iOS 原生页面 实现从 `Flutter` 页面跳转到 `iOS` 原生页面的方法 `pushNativeRoute`。这里根据 `pageName` 来判断生成哪个 `vc` 。可以用参数来控制是否展示跳转动画以及进入下一个页面的方式。进入下一个页面的方式可以是 `push` 方式或者 `present `方式。 **如图** ![10001](assets/10001.png) ### 4、从 iOS 原生页面跳转到 Flutter 页面 ![10002](assets/10002.png) ### 5、从 Flutter 页面跳转回原生页面 实现关闭 `Flutter` 页面的 `popRoute` 方法。 ``` /// 退出页面 func popRoute(_ options: FlutterBoostRouteOptions!) {} ``` ### 6、初始化 FlutterBoost ``` // 初始化 FlutterBoost let delegate = BoostDelegate.shared FlutterBoost.instance().setup(application, delegate: delegate) { engine in print("-----------") } ``` ![10003](assets/10003.png) ### 7、Demo演示 [Demo演示](https://gitee.com/FlutterProjects/flutter_boost_example/tree/master/BoostTestIOS) ## 五、FlutterBoost 工程中 Android 部分的配置代码 ### 1、配置 setting.gradle 文件 在 `setting.gradle` 文件, 添加 ``` setBinding(new Binding([gradle: this])) evaluate(new File( settingsDir.parentFile, 'flutter_module/.android/include_flutter.groovy' )) include ':flutter_module' project(':flutter_module').projectDir = new File('../flutter_module') ``` ![10004](assets/10004.png) ### 2、配置 build.gradle 文件 在`app`的`build.gradle`下添加如下代码 ``` implementation project(':flutter') implementation project(':flutter_boost') ``` ![10005](assets/10005.png) ### 3、配置 AndroidManifest.xml文件 还需要在清单文件中添加以下内容直接粘贴到``标签包裹的内部即可,也就是和其他``标签同级 ``` ``` ![10006](assets/10006.png) 然后点击右上角的`sync` 同步一下,就会开始一些下载和同步的进程,等待完成。 ### 4、在Application中添加FlutterBoost的启动流程,并设置代理 #### - 新建一个App类继承Application ``` public class App extends Application ``` ![10007](assets/10007.png) #### - AndroidManifest.xml 需要在 application 设置一下 ``` android:name=".App" ``` ![10008](assets/10008.png) ### 5、在 Android 原生页面设置逻辑 #### - 跳转到 Flutter 页面 ``` /** * 跳转到 Flutter 页面 */ private void gotoFlutter() { Map params = new HashMap(); params.put("data", data); FlutterBoostRouteOptions options = new FlutterBoostRouteOptions.Builder() .pageName("mainPage") .arguments(params) .build(); FlutterBoost.instance().open(options); } ``` #### - Flutter页面跳转到原生页面并接收来自 Flutter 页面的数据 ``` @Override public void pushNativeRoute(FlutterBoostRouteOptions options) { // 这里根据options.pageName来判断你想跳转哪个页面,这里简单给一个 if (options.pageName().equals("about")) { Intent intent = new Intent(FlutterBoost.instance().currentActivity(), AboutActivity.class); String data = (String) options.arguments().get("content"); intent.putExtra("content", data); FlutterBoost.instance().currentActivity().startActivityForResult(intent, options.requestCode()); } } ``` **下面是Flutter跳转到关于页面,并传递数据** ![10009](assets/10009.png) **下面原生页面跳转到Flutter页面** ![10010](assets/10010.png) ``` @Override public void pushFlutterRoute(FlutterBoostRouteOptions options) { Intent intent = new FlutterBoostActivity.CachedEngineIntentBuilder(FlutterBoostActivity.class) .backgroundMode(options.opaque() ? FlutterActivityLaunchConfigs.BackgroundMode.opaque : FlutterActivityLaunchConfigs.BackgroundMode.transparent) .destroyEngineWithActivity(false) .uniqueId(options.uniqueId()) .url(options.pageName()) .urlParams(options.arguments()) .build(FlutterBoost.instance().currentActivity()); FlutterBoost.instance().currentActivity().startActivity(intent); } ``` ### 6、Demo演示 [Demo演示](https://gitee.com/FlutterProjects/flutter_boost_example/tree/master/BoostTestAndroid) *** # 问题 ## 问题一 ``` caused by org.gradle.api.internal.plugins.pluginapplicationexception failed to apply plugin ``` **解决方法** ``` 找到settings.gradle,将FAIL_ON_PROJECT_REPOS修改为PREFER_PROJECT,保存重新Build。 dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() .... } } 修改后 dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.PREFER_PROJECT) repositories { google() mavenCentral() .... } } ```