开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情
前言
笔者最近在对 App 项目 Flutter Web 化落地中。路由建设上,在 Web 上肯定无法使用 flutter_boost,那显然的遇到了一些路由的相关改造。
这里把一些笔者感觉更为优雅的实现介绍给大家。
背景
go_router 作为官方路由(推荐),应该是我们使用和学习的对象。
其他选型上这里就不一一列举,掘金上很多关于 Flutter 路由的科普文章,写的都挺好的 ~
这里可能有些同学分不清哪些插件是官方库还是三方库,这里笔者使用的方式是去看他 github 所在仓库位置,是否是在 github.com/flutter 官方仓库下
go_router
更新也很频繁,不到10天就会有一个新版本,毕竟是官方在维护,如果有经常关注 Flutter issues 的同学,就会发现在 go_router
issues 下也有 darshankawar 这位老哥的身影 ~
另外说一句,当前 go_router
的版本是 5.2.0,但在 Flutter 3.0 ~ 3.3 之间最高只能用到 4.5.1。
但 go_router
问题也是有的,能查到的资料确实太少了,也是因为它正式出现还不到一年,大家可能还是比较少的选择它(很多同学会选择 GetX
全家桶,或者和笔者公司一样使用 flutter_boost
来做混合开发)。
下面就给大家介绍下笔者如何去解决遇到的一些问题。
问题
实现全局 context
在 go_router
中是需要 context
上下文来实现路由相关能力的。
但在 flutter_boost
的实现上并不需要 context
(跨端的路由控制)。
所以在 Web 化的过程中,需要在不改变 App 实现方式的基础上,提供出一套不需要 context
的 go_router
。
原理还是很简单,直接放实现代码,可以拿来直接用:
// ./routes/application.dart
/// 构造一个全局路由观察者
class GDNavigatorObserver extends NavigatorObserver {
static GDNavigatorObserver instance = GDNavigatorObserver();
}
// ./main.dart
void main() {
...
final GoRouter _router = GoRouter(
observers: [GDNavigatorObserver.instance], // 挂载观察者
routes: routes, // 路由定义
errorBuilder: (context, state) => errorPage(), // 错误页面
redirect: (state) => redirect(state), // 重定向页面
);
}
使用上,即可在任意位置,通过如下代码获取到当前页面的 context
var context = GDNavigatorObserver.instance.navigator?.context;
需要额外注意:context
的生命周期,比如要是用了 showBottomSheet
之类的本页弹出,不应该使用上面的 context.pop()
,这会导致整个页面返回,而应该使用
对于 Flutter 库中的基础路由 Navigator.of(context).pop()
。
增加 pop(result) 能力
对于 Flutter 库中的基础路由 Navigator.of(context)
,是有一种 B 页面 -> A 页面回传的方式的:
// A 页面
/// 会等待 B 页面回调的结果
var result = await Navigator.of(context).push(B);
// B 页面
/// 返回 A 页面,且可传任意类型的回调参数 result
Navigator.of(context).pop(result);
而 go_router
中却是不支持的,这在 issues 里也能找到具体说明: github.com/flutter/flu…
官方认为这可能是问题,但不急于去解决的(P5)。也可以看到快半年了,迭代了这么多个版本,并没有去解决。
那在笔者实际项目里,flutter_boost
也是提供了相应的 pop(result)
的能力,且项目中确实有页面使用到。那如何去实现这个能力呢?
这在 issues 中有位老哥提供了实现方式 github.com/MrOnyszko/p… 他是通过 fork go_router
进行侵入式改造,但官方不合他的代码肯定是有官方的考虑,那我们也不应该通过侵入的方式修改,毕竟这库更新这么那么的频繁 ...
那从具体实现上,封装一层方法出来来使用即可,关键代码如下:
// ./gaoding_native_bridge/router_provider.dart
// 路由封装
class RouterProvider extends IRouter {
/// 记录完成时的回调 <location,Future>
final Map<String, Completer<dynamic>> _completers =
<String, Completer<dynamic>>{};
@override
// 封装 pop 方法
Future<bool> pop({
Map<String, dynamic> result = const {},
...
}) async {
// 获取当前的 context
var context = GDNavigatorObserver.instance.navigator?.context;
// 判断是否可以 pop
if (context != null && context.canPop()) {
// 拿到当前的路径,作为 key
final path = GoRouter.of(context).location;
final Completer<dynamic>? completer = _completers[path];
if (completer != null) {
// 发起回调,给上一页面传递 result
completer.complete(result);
}
// 从 map 中移除
_completers.remove(path);
// 调用 GoRouter 的 pop
context.pop();
}
...
return true;
}
@override
// 封装 push 方法
Future push(...) async {
// 获取当前的 context
var context = GDNavigatorObserver.instance.navigator?.context;
if (context == null) {
return;
}
// GoRouter push 方法
context.pushNamed(...);
// 定义 Completer
final Completer<dynamic> completer = Completer<dynamic>();
// 塞入 map
_completers[GoRouter.of(context).location] = completer;
// 返回 future
return completer.future;
}
}
使用上,跟 Navigator
一致:
// A 页面
var result = await GDBridgeAPI.router.push();
// B 页面
GDBridgeAPI.router.pop(result);
GDBridgeAPI 的实现可以看看笔者的另一篇文章:Flutter Web - 优雅的兼容 Flutter App 代码
合理定义路由
这部分算是一个综合设计,在保持 App 上路由 URL 不变的前提下,扩展成 go_router
推荐的使用形式。
直接看代码:
// ./routes/define.dart
class RouterURL {
/// 名称
final String name;
/// 路径
final String path;
const RouterURL({
required this.name,
required this.path,
});
/// 首页
static RouterURL home = const RouterURL(
name: 'home',
path: '/',
);
...
}
/// App URL 路由映射
final navigateToFlutterRoutes = {
'gaoding://home': RouterURL.home,
...
};
// ./routes/routes.dart
final routes = [
// 首页
GoRoute(
name: RouterURL.home.name,
path: RouterURL.home.path,
builder: (context, state) => const GDRootPage(),
),
...
];
简单示例,routes.dart
定义整个项目中 go_router
路由与页面的关系。 define.dart
定义各个常量,用于页面间使用。
context.push(context.namedLocation(
RouterURL.home.name,
));
navigateToFlutterRoutes
用于关联 App 和 go_router
路由之间的关系。
总结
这里想谈谈如何去更好的熟悉使用开源库,毕竟去等待有人发相关的文章,那真的不如趁早使用资料多的[狗头]。
而对于这样资料少的开源库(一般只看官方库),笔者一般第一步也是从 github 中看它们的使用示例(examples)以及 issues 来寻找解决问题的方案。
再找不到解决思路,才会去看源码实现,毕竟官方库的代码设计也是值得学习的。
如果对你开发学习上有丝丝作用,请点个赞[开心] ~
(十分感谢评论老哥提的建议,补充了一些注释说明)