如何在GoRoute的onExit回调中获取导航返回结果?
如何在GoRoute的onExit回调中获取导航返回结果?
我完全懂你遇到的这个痛点——用go_router做模态弹窗路由时,明明GoRouterDelegate._handlePopPageWithRouteMatch里能拿到pop的返回结果,却不把它暴露给onExit回调,想在拦截误退出的时候区分是用户主动点击按钮关闭,还是点遮罩层误触,确实挺闹心的。
给你分享几个社区里常用的可行方案,亲测能用:
方案一:页面状态同步法
核心思路是在弹窗页面的状态里提前存储pop的结果,让onExit能通过全局Key访问到这个值:
- 首先在弹窗页面的State类里定义存储变量和状态标识:
class ModalPage extends StatefulWidget { const ModalPage({super.key}); @override State<ModalPage> createState() => ModalPageState(); } class ModalPageState extends State<ModalPage> { // 用来存储pop的结果 bool? _popResult; // 标记是否有未保存更改 bool _hasUnsavedChanges = false; // 当页面内容修改时,把这个标记设为true void _onContentChanged() { setState(() { _hasUnsavedChanges = true; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('模态弹窗')), body: Column( children: [ TextField(onChanged: (_) => _onContentChanged()), Row( children: [ // 确认按钮:先赋值再pop ElevatedButton( onPressed: () { _popResult = true; Navigator.of(context).pop(true); }, child: const Text('确认'), ), // 取消按钮同理 ElevatedButton( onPressed: () { _popResult = false; Navigator.of(context).pop(false); }, child: const Text('取消'), ), ], ), ], ), ); } }
- 定义GoRoute时,通过全局Key获取页面状态,在
onExit里读取结果:
// 提前创建全局Key来获取页面状态 final modalPageKey = GlobalKey<ModalPageState>(); GoRoute( path: '/modal-page', pageBuilder: (context, state) => MaterialPage( fullscreenDialog: true, child: ModalPage(key: modalPageKey), ), onExit: (context) async { final result = modalPageKey.currentState?._popResult; // 结果为null,说明是点击遮罩层触发的退出 if (result == null && modalPageKey.currentState?._hasUnsavedChanges == true) { // 弹出确认对话框,询问用户是否要放弃未保存内容 final shouldExit = await showDialog<bool>( context: context, builder: (context) => AlertDialog( title: const Text('未保存的更改'), content: const Text('离开后未保存的内容会丢失,确定要离开吗?'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text('取消'), ), TextButton( onPressed: () => Navigator.of(context).pop(true), child: const Text('确定'), ), ], ), ); return shouldExit ?? false; } // 允许正常退出 return true; }, );
方案二:自定义导航观察者法
通过NavigatorObserver监听全局的pop动作,捕获路由结果后存在全局存储里,onExit时直接读取:
- 自定义一个观察者类,专门用来记录指定路由的pop结果:
class PopResultObserver extends NavigatorObserver { final Map<String, dynamic?> _routeResults = {}; @override void didPop(Route route, Route? previousRoute) { super.didPop(route, previousRoute); // 只记录目标模态路由的结果 if (route.settings.name == '/modal-page' && route is MaterialPageRoute) { _routeResults['/modal-page'] = route.result; } } // 获取指定路由的pop结果 dynamic? getResult(String routePath) => _routeResults[routePath]; // 用完后清空,避免缓存干扰 void clearResult(String routePath) => _routeResults.remove(routePath); }
- 初始化GoRouter时添加这个观察者,然后在
onExit里读取结果:
// 创建观察者实例 final popObserver = PopResultObserver(); final router = GoRouter( observers: [popObserver], routes: [ GoRoute( path: '/modal-page', pageBuilder: (context, state) => MaterialPage( fullscreenDialog: true, child: const ModalPage(), ), onExit: (context) async { final result = popObserver.getResult('/modal-page'); popObserver.clearResult('/modal-page'); if (result == null) { // 遮罩层退出的情况,检查未保存内容 // 这里可以根据实际情况获取页面的未保存状态,比如用状态管理工具 final hasUnsaved = true; // 替换成你的未保存状态判断逻辑 if (hasUnsaved) { final shouldExit = await showDialog<bool>( context: context, builder: (context) => AlertDialog( title: const Text('未保存的更改'), content: const Text('离开后未保存的内容会丢失,确定要离开吗?'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text('取消'), ), TextButton( onPressed: () => Navigator.of(context).pop(true), child: const Text('确定'), ), ], ), ); return shouldExit ?? false; } } return true; }, ), ], );
其实关于官方为什么不把result传给onExit,社区里也有不少讨论,大概率是因为onExit的设计初衷是做退出拦截,而非处理返回结果,但确实这个场景非常普遍,说不定后续版本会补上这个参数。
内容来源于stack exchange
相关产品推荐
相关产品推荐

