Flutter嵌套Navigator注销重登录报Duplicate GlobalKey错误
问题场景
- 采用嵌套Navigator实现多模块独立路由:定义
NavigatorKeys类,通过静态常量声明首页、班次、申请、消息、个人中心5个模块对应的GlobalKey<NavigatorState> - 以个人中心模块
ProfileNavigator为例:该StatelessWidget绑定profileNavigatorKey,通过onGenerateRoute处理模块内部路由跳转 - 底部导航采用自定义
BottomBar实现,按配置动态加载各模块Navigator作为tab切换页面;通过WillPopScope拦截安卓返回事件,根据当前选中tab对应的NavigatorKey处理子路由pop逻辑,子路由无可pop内容时才触发应用退出 - 异常触发逻辑:用户注销时通过rootNavigator跳转至登录页,重新登录后持续抛出Duplicate GlobalKey detected in widget tree错误,错误首发于
ProfileNavigator,调试控制台无限循环打印报错
根本原因
该错误本质是同一个GlobalKey在widget树中同时被多个组件持有绑定:
注销跳转登录页时,仅通过rootNavigator做了push操作,未清空路由栈中承载底部tab和嵌套Navigator的主页组件,旧的各个模块Navigator实例并未被销毁,仍然和静态GlobalKey保持绑定关系。重新登录后再次进入主页时,会重新build生成一套新的模块Navigator组件,尝试绑定同一个静态GlobalKey,直接触发框架的GlobalKey唯一性校验。因为Profile模块的路由逻辑通常在登录后会自动触发用户信息拉取、权限校验等build操作,所以会最先抛出错误,后续持续build导致报错无限循环。
修复方案
按优先级依次处理:
- 修正注销跳转逻辑,清空旧主页路由栈
注销跳登录页时不要使用普通push,使用pushNamedAndRemoveUntil把主页及以上的所有路由全部清除,保证旧的嵌套Navigator组件完整执行dispose流程,释放GlobalKey的绑定关系:// 注销时的跳转逻辑,rootNavigator设为true确保操作作用于顶层路由栈 Navigator.of(context, rootNavigator: true) .pushNamedAndRemoveUntil('/login', (route) => false); - 补全GlobalKey调用的安全校验
所有通过NavigatorKey操作子路由的逻辑(包括WillPopScope中的pop拦截、模块内跨tab跳转),在调用currentState前先校验组件挂载状态,避免持有旧context导致组件无法被GC回收:// WillPopScope中的返回拦截逻辑示例 final currentTabKey = NavigatorKeys.profileNavigatorKey; // 替换为当前选中tab对应的key if (currentTabKey.currentState != null && currentTabKey.currentState!.mounted) { final canPop = await currentTabKey.currentState!.maybePop(); if (canPop) return false; } return true; - 身份变更时强制重建嵌套Navigator
如果业务需要保留登录前的部分页面状态、无法清空整个主页栈,可以在用户身份状态(登录/注销)变更时,给所有模块的Navigator新增一个和用户身份绑定的ValueKey,强制框架重建所有嵌套Navigator实例,解除旧Key的绑定冲突:// 主页build时给每个模块Navigator传参 ProfileNavigator( key: ValueKey('profile_${currentUser?.id ?? 'guest'}'), ) - 避免嵌套Navigator被意外保活
检查底部tab切换逻辑,如果使用IndexedStack/Offstage做页面保活,注销时需要主动把IndexedStack的子组件列表清空,不要保留旧的Navigator实例在widget树中。
内容的提问来源于stack exchange,提问作者Shahroz Javed
相关产品推荐
相关产品推荐

