开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 7 天,点击查看活动详情
本文翻译自: riverpod_navigator | Flutter Package (flutter-io.cn)
译时版本:1.0.10
无 GUI 的开发和测试
可以开发和测试导航逻辑而无需指定单个 Flutter 组件:
test('navigation model', () async {
final container = ProviderContainer(
overrides: riverpodNavigatorOverrides([HomeSegment()], AppNavigator.new),
);
final navigator = container.read(navigatorProvider);
Future navigTest(Future action(), String expected) async {
await action();
await container.pump();
expect(navigator.navigationStack2Url, expected);
}
await navigTest(
() => navigator.navigate([HomeSegment(), BookSegment(id: 1)]),
'home/book;id=1',
);
await navigTest(
() => navigator.pop(),
'home',
);
await navigTest(
() => navigator.push(BookSegment(id: 2)),
'home/book;id=2',
);
await navigTest(
() => navigator.replaceLast<BookSegment>((old) => BookSegment(id: old.id + 1)),
'home/book;id=3',
);
});
URL 解析
Flutter Navigator 2.0 和 它的 MaterialApp.router 构造方法需要一个 URL 解析器 (RouteInformationParser)。 我们使用 URL 语法,查看 RFC 3986 的 3.3. 章节 ,注意 *例如,一个 URI 生成器会使用如 "name;v=1.1"..." 之类的段。
Each TypedSegment must be converted to string-segment and back. The format of string-segment is
<unique TypedSegment id>[;<property name>=<property value>]*, e.g. book;id=3.
编码/解码示例:
代替直接和字符串相互转换,这里是和 typedef UrlPars = Map<String,String> 相互转换。
至今为止,我们支持类型段属性的以下类型: int, double, bool, String, int?, double?, bool?, String? 。
class TestSegment extends TypedSegment {
const TestSegment({required this.i, this.s, required this.b, this.d});
factory TestSegment.decode(UrlPars pars) => TestSegment(
i: pars.getInt('i'),
s: pars.getStringNull('s'),
b: pars.getBool('b'),
d: pars.getDoubleNull('d'),
);
@override
void encode(UrlPars pars) =>
pars.setInt('i', i).setString('s', s).setBool('b', b).setDouble('d', d);
final int i;
final String? s;
final bool b;
final double? d;
}
用 RRoute<TestSegment>('test',TestSegment.decode 注册 测试段后,下面的URL是正确的:
- test;i=1;b=true
- test;i=2;b=true;d=12.6;s=abcd
- test;i=2;b=true/test;i=2;b=true;d=12.6;s=abcd/test;i=3;b=false
定制化
URL 转换的各个要素都可以定制化,如。
-
支持其它的属性类型(作为 DateTime, 提供了 getDateTime、 getDateTimeNull 和 setDateTime 在自己写的 UrlPars 扩展中)
在 path_parser.dart 查看
UrlPar 的扩展 UrlParsEx。 -
重写整个 IPathParser ,并且使用完全不同的 URL 语法。然后在 AppNavigator 使用自定义的解析器:
class AppNavigator extends RNavigator {
AppNavigator(Ref ref)
: super(
....
pathParserCreator: (router) => MyPathParser(router),
...
替换 AppNavigator 的导航事件
替换 AppNavigator 中所有事件(特别是导航)的代码是好的实践。 它们不只可以用于编写页面组件,也可以用于测试。
class AppNavigator extends RNavigator {
......
/// navigate to next book
Future toNextBook() => replaceLast<BookSegment>((last) => BookSegment(id: last.id + 1));
/// navigate to home
Future toHome() => navigate([HomeSegment()]);
}
在界面组件中,如下使用:
...
ElevatedButton(
onPressed: navigator.toNextBook,
child: Text('Book $id'),
),
...
在测试代码中,如下使用:
await navigTest(navigator.toNextBook, 'home/book;id=3');
异步导航
异步导航意味着导航是在异步动作之后延迟进行的。 每个页面的这些动作是:
- 打开 (在打开一个新的页面之前)
- 关闭 (在关闭旧的页面之前)
- 替换 (用相同段类型替换页面之前)
打开 和 关闭 动作返回异步的结果,可在后面构建新页面时使用。
为类型段定义类
对类型段使用合适的类型(String)的 AsyncSegment 混入。
class HomeSegment extends TypedSegment with AsyncSegment<String>{
....
}
class BookSegment extends TypedSegment with AsyncSegment<String>{
....
}
配置导航
为 RRoute 添加 opening、 closing 或 replacing 动作actions to RRoute 。
class AppNavigator extends RNavigator {
AppNavigator(Ref ref)
: super(
ref,
[
RRoute<HomeSegment>(
'home',
HomeSegment.decode,
HomeScreen.new,
opening: (sNew) => sNew.setAsyncValue(_simulateAsyncResult('Home.opening', 2000)),
),
RRoute<BookSegment>(
'book',
BookSegment.decode,
BookScreen.new,
opening: (sNew) => sNew.setAsyncValue(_simulateAsyncResult('Book ${sNew.id}.opening', 240)),
replacing: (sOld, sNew) => sNew.setAsyncValue(_simulateAsyncResult('Book ${sOld.id}=>${sNew.id}.replacing', 800)),
closing: (sOld) => Future.delayed(Duration(milliseconds: 500)),
),
],
);
....
}
// 模拟动作,如保存到外部存储/从外部存储加载
Future<String> _simulateAsyncResult(String asyncResult, int msec) async {
await Future.delayed(Duration(milliseconds: msec));
return '$asyncResult: async result after $msec msec';
}
构建页面时使用异步动作的结果
...
Text('Async result: "${segment.asyncValue}"'),
...
查看:
其它特性和示例
示例安装
克隆 riverpod_navigator repository 之后,跳转到examples/doc 子目录并执行:
flutter create .flutter pub get
查看示例的 /lib 子目录。
导航数据流图
正如所见,Input state(输入状态) 的改变启动了异步计算。计算的结果是Output state(输出状态),它带有应用特定的 Side effect(副作用)。然后 Navigator 2.0 RouterDelegate 使用 navigationStackProvider 进行同步。
路线图
我(作者)为了新项目准备了这个包。会根据社区是否使用它决定是否进行进一步的开发。
- 校对工作,因为英语不好。非常欢迎社区的帮助。
- 对 Cupertino 进行参数化
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 7 天,点击查看活动详情