关于GoRouter中Codec传递的Extra参数回退时无法修改数据的问题咨询
看起来你现在遇到的核心问题是:之前用普通对象传引用的时候,编辑页改完数据,列表页能直接拿到更新;但现在为了适配GoRouter的extra传递(用了Codec编码成JSON字符串),变成传副本了,编辑完成后只能通过pop(isValid)返回一个布尔值,没法直接把修改后的example和activeFoos回传给列表页,而且你想找个非Pop返回值的方式来传递更新后的数据?
首先得明确:GoRouter的extra参数是一次性的初始化数据,路由栈里的state.extra是创建路由时的快照,没法在目标页修改后反向更新前一页的路由状态,所以直接修改extra回传的路子走不通。不过针对你的Codec+Hive架构,有几个适配性很强的替代方案:
1. 扩展Pop的返回值,传递完整修改后数据(最推荐,符合GoRouter设计)
你现在只返回了isValid布尔值,其实可以把验证后的example和activeFoos编码后和布尔值一起包装返回,完全兼容你现在的编码逻辑:
编辑页(ExampleEditPage)修改保存逻辑
await widget.editVM.validateExample.executeWithFuture(); bool isValid = widget.editVM.exampleValid; if(context.mounted){ // 把验证后的对象编码成JSON,和isValid一起封装 final updatedData = jsonEncode({ 'isValid': isValid, 'updatedExample': const ExamplesCodec().encode(widget.editVM.example), 'updatedActiveFoos': widget.editVM.activeFoos .map((e) => const FooIDCodec().encode(e)) .toList(), }); GoRouter.of(context).pop(updatedData); }
列表页(ExamplePage)接收返回值
final result = await GoRouter.of(context).pushNamed( 'exampleEditPage', extra: jsonEncode({ 'example': const ExamplesCodec().encode(widget.mainVM.exampleCopy), 'activeFoos': widget.mainVM.activeFoosCopy .map((e) => const FooIDCodec().encode(e)) .toList(), }) ); // 解析返回的完整数据 if (result != null) { final decodedResult = jsonDecode(result as String) as Map<String, dynamic>; bool isValid = decodedResult['isValid']; if (isValid && context.mounted) { // 解码更新后的对象,更新列表页的VM widget.mainVM.exampleCopy = const ExamplesCodec().decode(decodedResult['updatedExample']); widget.mainVM.activeFoosCopy = (decodedResult['updatedActiveFoos'] as List) .map((e) => const FooIDCodec().decode(e)) .toList(); // 保存到Hive await Hive.box(...).put(widget.mainVM.exampleCopy, widget.mainVM.activeFoosCopy); } }
这个方案完全符合GoRouter的导航设计(push对应pop返回结果),而且能配合你现有的Codec编码逻辑,还避免了直接传引用可能带来的“意外修改未验证数据”的风险。
2. 共享ViewModel状态(适合你的MVVM架构)
既然你已经用了ViewModel(mainVM、editVM),可以让编辑页直接操作列表页的mainVM,验证通过后直接更新状态,不用来回传数据:
编辑页保存逻辑调整
await widget.editVM.validateExample.executeWithFuture(); bool isValid = widget.editVM.exampleValid; if (isValid && context.mounted) { // 直接更新列表页的VM副本 widget.mainVM.exampleCopy = widget.editVM.example; widget.mainVM.activeFoosCopy = widget.editVM.activeFoos; // 直接保存到Hive await Hive.box(...).put(widget.mainVM.exampleCopy, widget.mainVM.activeFoosCopy); GoRouter.of(context).pop(true); }
这种方式不用来回编码传数据,效率更高,但要注意必须在验证通过后再更新主VM状态,避免把未验证的脏数据带到列表页。
3. 结合GoRouter的刷新监听+状态管理
如果你想通过路由刷新来同步状态,可以把mainVM作为GoRouter的refreshListenable,编辑页保存后通知路由刷新,列表页自动同步最新数据:
初始化GoRouter时配置刷新监听
final goRouter = GoRouter( refreshListenable: mainVM, // 你的mainVM需要继承ChangeNotifier routes: [/* 你的路由配置 */], );
编辑页保存后触发刷新
await widget.editVM.validateExample.executeWithFuture(); bool isValid = widget.editVM.exampleValid; if (isValid && context.mounted) { // 先保存到Hive await Hive.box(...).put(widget.editVM.example, widget.editVM.activeFoos); // 通知mainVM刷新,GoRouter会同步刷新路由栈中的页面 widget.mainVM.notifyListeners(); GoRouter.of(context).pop(true); }
列表页监听状态变化
列表页可以通过mainVM的addListener或者Consumer(如果你用Provider)来监听变化,自动更新UI或重新加载数据。
关于你提到的那个Flutter GoRouter的Issue,虽然没看到具体内容,但大概率是和extra参数的序列化、单向传递特性相关的问题,上面的方案应该能覆盖这类场景的需求。
最后再总结下:GoRouter的extra是单向的初始化参数,没法反向修改回传,所以最优解还是通过Pop返回完整的修改后数据,既符合路由设计规范,又能完美配合你现在的Codec+Hive架构。
内容来源于stack exchange

