Flutter插件muffin的介绍与使用
Flutter插件muffin的介绍与使用
Multiple Flutters In,基于Flutter2.0多Engine、Navigator2.0实现的一套混合栈管理方案。
与单Engine的本质区别在于,单Engine模式下pop或者push操作的是同一个Flutter路由,同一个Engine需要attach到不同的FlutterVC上,导致混合栈维护复杂。多Engine模式下,Engine是底层spawn的,一个FlutterVC对应一个Engine,每一个FlutterVC中的Flutter路由保证独立,混合栈维护简单,可以实现类似popUntil的功能。
Flutter插件muffin的功能
- Muffin API 无
Context
- Flutter 树形结构 路由配置
Native push Flutter
携带参数、获取返回值- 自定义
UrlParser
(定义Uri解析)实现 从 Schema 跳转 Flutter Flutter push Native
携带参数、获取返回值- 自定义
PushNativeHandler
(灵活根据path跳转) Flutter pop
携带参数Flutter popUntil
携带参数- 数据同步共享,实现原生和
Flutter
一些类数据改变同步 Android Fragment
级别支持- 单独运行
module
时,可为 原生通信事件自定Mock
数据 - 支持自定义
MethodChannel
- 支持 二级路由 配置,比如
/home/detail
、/home/first/:id
携带参数
TODO
Native
页面路由标记方式,目前使用Class类名作为path。兼容ARouter
- 仍然使用 Navigator API 可能会遇到问题
API
Native push Flutter
// 基本push
MuffinNavigator.push("/home");
// push携带参数
Map<String, Object> arguments = new HashMap<>();
arguments.put("count", 1);
MuffinNavigator.push("/first", arguments);
// push scheme
MuffinNavigator.push("meijianclient://meijian.io?url=first&name=uri_test");
// push scheme 携带参数,将会拼接 query 参数
Map<String, Object> arguments = new HashMap<>();
arguments.put("count", 1);
MuffinNavigator.push("meijianclient://meijian.io?url=first&name=uri_test");
// 对应都有pushForResult API
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (data != null) {
// pageName 为返回参数的 页面标示
Log.e("AAA", data.getStringExtra("pageName"));
// result为 返回值 是一个 HashMap<String,Object>
Log.e("AAA", (data.getSerializableExtra("result")).toString());
}
}
Flutter push Flutter
// 与平常使用的Navigator API一样
// pushNamed
Muffin.pushNamed('/second', {'data': "data from Home Screen"});
// 获取返回值 使用 await 即可
Flutter push Native
// 将页面路径和参数传递到原生,原生根据参数跳转,可以通过ARouter,可以if else 判断 等
// pushNamed
Muffin.pushNamed('/native_main', {'data': "data from Home Screen"});
Flutter pop
// pop
Muffin.pop({'data': "data from Home Screen"});
// TODO popUntil
Muffin.popUntil('/first', {'data': "data from Home Screen"});
数据同步
// 原理是监听字段改变,通知所有监听者
// 实现DataModelChangeListener接口,添加被监听字段,监听器等
// 具体参考 Demo [BasicInfo] 类
// Flutter侧也需要同样字段的类,实现 DataModelChangeListener接口
// 具体参考 Flutter Demo [basic_info.dart]
Mock
// 在单独运行时,当需要调用 method channel 的方法时,因为原生没有注册方法,导致报错。我们在调用通信事件时进行拦截,返回 mock 的数据。
// 在Flutter初始化时 配置 mock 数据
/// getArguments:事件名,(key,value):(事件名,参数)=> dynamic(具体类型)
Muffin.addMock(MockConfig('getArguments', (key, value) => {});
Android接入Muffin
-
在Flutter项目中添加依赖
dependencies: muffin: ^1.0.0
-
路由配置&&数据共享配置&&各种配置
void main() async { /// 确保channel初始化 WidgetsFlutterBinding.ensureInitialized(); /// 如果需要数据同步,则添加下面的代码,将原生的数据同步到Flutter侧 await Muffin.initShare([BasicInfo.instance]); /// 添加 channel method mock Muffin.addMock(MockConfig('someMethod', (key, value) => {})); /// get Navigator Widget runApp(await getApp()); } /// get a App with dif initialRoute Future<Widget> getApp() async { /// 从原生跳转到Flutter第一次,需要的一些参数 var arguments = await NavigatorChannel.arguments; return MuffinMaterialApp( /// 标识当前是否为混合模式;纯Flutter模式将不会走混合栈通信;默认为true multiple: false, notFoundRoute: MuffinPage(name: '/404', page: () => NotFoundPage()), /// 自定义Parser routeInformationParser: MuffinInformationParser(MeiJianUrlParser(), initialRoute: arguments.path, arguments: arguments.arguments), /// 路由树 muffinPages: [ /// /home/first and /home/second MuffinPage(name: '/home', page: () => HomeScreen(), children: [ MuffinPage( name: '/first', page: () => FirstScreen(), ), MuffinPage(name: '/second/:id', page: () => SecondScreen()), ]) ]); } /// 自定义Schema 解析 class MeiJianUrlParser extends UrlParser { [@override](/user/override) Map<String, String> getParams(Uri uri) { Map<String, String> params = Map.from(uri.queryParameters); params.remove('url'); return params; } [@override](/user/override) String getPath(Uri uri) { if (uri.host.isEmpty) { return uri.path; } String path = uri.queryParameters['url']!; if (path.isEmpty) { path = "/"; } if (!path.startsWith("/")) { path = "/" + path; } return path; } }
-
原生,在Application中初始化 Muffin
// 普通初始化,第二个参数为 各种提供给上层的接口实现 Muffin.init(this, options()); private Muffin.Options options() { // 数据同步对象 List<DataModelChangeListener> models = new ArrayList<>(); models.add(BasicInfo.getInstance()); return new Muffin.Options() // Flutter 跳转 Native 时提供给上层的接口 .setPushNativeHandler((activity, pageName, data) -> { // 根据 pageName 和 data 拼接成 schema 跳转 if (TextUtils.equals("/main", pageName)) { Intent intent = new Intent(activity, MainActivity.class); activity.startActivity(intent); } }) // 带有数据同步能力 .setModels(models) // 新增自定义VC,使用【MuffinFlutterFragment】, 参考[BaseFlutterActivity] // 默认使用【MuffinFlutterActivity】 .setAttachVc(BaseFlutterActivity.class); }
-
在Manifest.xml文件中配置FlutterActivity
-
好了,Muffin已经集成完了。
补充
Flutter页面获取参数
// 老旧的方式,是在混合栈路由配置时,作为参数传递到对应的页面,新的方式 汲取了 Getx的经验,可以直接使用 Muffin 的API获取
// 参考 first.dart
Text('Get arguments by [Muffin.arguments]',
style: Theme.of(context).textTheme.subtitle1,),
/// Muffin.arguments
Text('${Muffin.arguments}',
style: Theme.of(context).textTheme.subtitle1, ),
时序图 / 结构图
完整示例Demo
以下是一个完整的示例Demo,展示如何使用muffin
插件来实现Flutter与原生之间的交互:
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:muffin/navigator/information_parser.dart';
import 'package:muffin/navigator/page_route.dart';
import 'package:muffin/root/muffin_material_app.dart';
import 'package:muffin_example/basic_info.dart';
import 'package:muffin_example/second.dart';
import 'first.dart';
import 'home.dart';
import 'package:muffin/muffin.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// sharing data
Muffin.initShare([BasicInfo.instance]);
// 添加自定义 mock 数据
Muffin.addMock(MockConfig('getArguments', (key, value) => {}));
runApp(await getApp());
}
Future<Widget> getApp() async {
var arguments = await NavigatorChannel.arguments;
print(arguments);
return MuffinMaterialApp(
notFoundRoute: MuffinPage(name: '/404', page: () => NotFoundPage()),
routeInformationParser: MuffinInformationParser(MeiJianUrlParser(),
initialRoute: arguments.path, arguments: arguments.arguments),
muffinPages: [
/// /home/first and /home/second
MuffinPage(name: '/home', page: () => HomeScreen(), children: [
MuffinPage(
name: '/first',
page: () => FirstScreen(),
),
MuffinPage(name: '/second/:id', page: () => SecondScreen()),
])
]);
}
class SplashLoading extends StatefulWidget {
const SplashLoading({Key? key}) : super(key: key);
[@override](/user/override)
_SplashLoadingState createState() => _SplashLoadingState();
}
class _SplashLoadingState extends State<SplashLoading> {
[@override](/user/override)
void initState() {
super.initState();
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CupertinoActivityIndicator(),
),
);
}
}
class NotFoundPage extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.red.withOpacity(0.6),
body: Container(
child: Center(
child: Text(
'哎哟、没有找到哦',
style: Theme.of(context).textTheme.subtitle1,
),
),
),
);
}
}
class MeiJianUrlParser extends UrlParser {
[@override](/user/override)
Map<String, String> getParams(Uri uri) {
Map<String, String> params = Map.from(uri.queryParameters);
params.remove('url');
return params;
}
[@override](/user/override)
String getPath(Uri uri) {
if (uri.host.isEmpty) {
return uri.path;
}
String path = uri.queryParameters['url']!;
if (path.isEmpty) {
path = "/";
}
if (!path.startsWith("/")) {
path = "/" + path;
}
return path;
}
}
更多关于Flutter插件muffin的介绍与使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html