Flutter集成GoRouter与Firebase推送时如何传递通知附加数据至路由拦截
Firebase Messaging + GoRouter 深度链接附加数据传递方案
核心逻辑很简单:不要完全依赖系统自动触发通知深度链接跳转,在FCM的通知点击回调里提前提取附加业务数据存到全局临时状态,再手动触发GoRouter跳转,这样redirect拦截阶段就能直接读取数据做账号校验。
1. 先建一个轻量的全局临时存储
专门用来存当前待跳转通知携带的附加参数,不需要引入复杂状态管理,单例即可:
class NotificationRouteCache { NotificationRouteCache._(); static final NotificationRouteCache instance = NotificationRouteCache._(); // 通知携带的目标用户ID,空值代表当前不是通知触发的跳转 String? pendingUserId; // 校验完成/跳转结束后清空缓存,避免影响后续普通路由跳转 void reset() => pendingUserId = null; }
2. 改造FCM通知点击监听逻辑
覆盖两种通知打开场景:后台唤醒、冷启动打开,先解析附加数据存缓存,再手动触发路由跳转,不要等系统自动处理link跳转:
// 处理应用在后台、未被杀死时,点击通知唤醒的场景 FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage msg) { final rawLink = msg.notification?.link?.toString(); if (rawLink == null) return; // 先存附加数据 NotificationRouteCache.instance.pendingUserId = msg.data['userId']; // 解析link对应的应用内路径,去掉域名前缀 final targetPath = Uri.parse(rawLink).path; // 手动触发GoRouter跳转 GoRouter.of(navigatorKey.currentContext!).go(targetPath); }); // 处理应用完全终止时,点击通知冷启动打开的场景 FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage? msg) async { if (msg == null) return; final rawLink = msg.notification?.link?.toString(); if (rawLink == null) return; NotificationRouteCache.instance.pendingUserId = msg.data['userId']; final targetPath = Uri.parse(rawLink).path; // 等应用首帧渲染完成、GoRouter初始化完毕再跳转,避免上下文报错 WidgetsBinding.instance.addPostFrameCallback((_) { GoRouter.of(navigatorKey.currentContext!).go(targetPath); }); });
注意:这里建议把你之前配置的、监听系统深度链接流自动跳转的逻辑统一收口,所有路由跳转都走GoRouter的
go方法,避免出现重复跳转、拦截逻辑不生效的问题。普通非通知触发的深度链接(比如外部网页跳APP、adb调起)因为缓存里没有pendingUserId,不会触发额外的账号校验逻辑,和原有行为完全一致。
3. 在GoRouter的redirect拦截层加校验逻辑
直接读缓存里的待校验用户ID,和你原有的登录态校验逻辑合并就行:
GoRouter( navigatorKey: navigatorKey, // 记得传全局navigatorKey,方便全局拿context observers: [_routeObserver], initialLocation: '/', routes: [ // 你的原有路由配置 ], refreshListenable: GoRouterRefreshStream(_loginState.stream), redirect: (state) { // 原有登录态校验逻辑 final currentUser = _authService.currentUser; if (currentUser == null && state.subloc != '/login') { return '/login?redirect=${state.subloc}'; } // 新增:通知跳转的账号匹配校验 final targetUserId = NotificationRouteCache.instance.pendingUserId; if (targetUserId != null) { // 当前登录用户和通知所属用户不一致,跳转到账号切换页 if (currentUser?.id != targetUserId) { // 把目标路由、目标用户ID当参数传给切换页,切换完成后直接跳目标页 return '/switch-account?targetUserId=$targetUserId&redirect=${state.subloc}'; } // 校验通过,清空缓存避免影响后续跳转 NotificationRouteCache.instance.reset(); } // 无特殊逻辑走原有路由 return null; } );
4. 账号切换完成后的收尾
在账号切换页完成登录/切号操作后,清空缓存再跳转到目标路径即可:
// 切号成功后的回调 void onAccountSwitched(String targetPath) { NotificationRouteCache.instance.reset(); GoRouter.of(context).go(targetPath); }
几个踩坑提醒
- 不要把通知附加数据塞到路由query参数里传递,一方面会污染路由URL,另一方面如果原link本身带业务参数很容易出现参数冲突,临时缓存的方案更轻量无侵入
- 冷启动场景一定要等首帧渲染完再触发跳转,不然会出现GoRouter未初始化、上下文为空的报错
- 每次校验通过/跳转完成一定要清空缓存,不然后续普通路由跳转可能会误触发账号校验逻辑
内容的提问来源于stack exchange,提问作者Petr Nymsa
相关产品推荐
相关产品推荐

