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

  1. 在Flutter项目中添加依赖

    dependencies:
      muffin: ^1.0.0
    
  2. 路由配置&&数据共享配置&&各种配置

    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;
      }
    }
    
  3. 原生,在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);
    }
    
  4. 在Manifest.xml文件中配置FlutterActivity

  5. 好了,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

回到顶部