Flutter GoRouter+Riverpod重定向异常:Location为空且未触发重定向
问题描述
使用Riverpod做状态管理、GoRouter做路由管理,需求是refreshToken刷新失败时自动重定向到登录页。当前遇到的问题:
- 首次在未授权状态下访问页面时,会抛出
Location cannot be empty.的GoException,重定向逻辑未及时触发 - 再次访问页面时,重定向才能成功生效
尝试过的方案:监听refreshTokenViewModelProvider,将状态同步到ValueNotifier并作为GoRouter的refreshListenable,但问题依旧。
相关代码与日志
Router Provider代码
final _key = GlobalKey<NavigatorState>(); final routerProvider = Provider<GoRouter>((ref) { ***//使用watch时应用卡在启动页** //final authState = ref.read(refreshTokenViewModelProvider); final authStateNotifier = ValueNotifier(RefreshTokenState.initial); ref ..onDispose(authStateNotifier.dispose) ..listen(refreshTokenViewModelProvider, (_, value) { logger.w("listener value state ${value.refreshTokenState}"); logger.d("listener authStateNotifier value ${authStateNotifier.value}"); authStateNotifier.value = value.refreshTokenState; logger.d("listener auth value after modification ${authStateNotifier.value}"); }); return GoRouter( navigatorKey: _key, debugLogDiagnostics: true, initialLocation: '/', // refreshListenable: authState, ***//有无此配置均无变化*** refreshListenable: authStateNotifier, routes: [....], errorBuilder: (context, state) => const Center(child: CircularProgressIndicator()), redirect: (context, state) { //此处无法使用watch,会报错Cannot use ref functions after the dependency of a provider changed but before the provider rebuilt // bool isRefreshTokenFailure = (authState.refreshTokenState == RefreshTokenState.failure); bool isRefreshTokenFailure = (authStateNotifier.value == RefreshTokenState.failure); logger.f("is ref token failure ? ........ ${isRefreshTokenFailure}"); if (isRefreshTokenFailure) { return "/login"; } else { return null; } }, ); });
首次访问日志
I/flutter (14770): │ ! listener value state RefreshTokenState.failure I/flutter (14770): │ 🐛 listener authStateNotifier value RefreshTokenState.initial I/flutter (14770): │ 🐛 listener auth value after modification RefreshTokenState.failure ======== Exception caught by foundation library ==================================================== The following GoException was thrown while dispatching notifications for GoRouteInformationProvider: Location cannot be empty. When the exception was thrown, this was the stack: #0 canonicalUri (package:go_router/src/path_utils.dart:125:5) #1 RouteConfiguration.findMatch (package:go_router/src/configuration.dart:296:31) #2 _RouteMatchListDecoder.convert (package:go_router/src/match.dart:947:23) #3 Codec.decode (dart:convert/codec.dart:30:34) #4 GoRouteInformationParser.parseRouteInformationWithDependencies (package:go_router/src/parser.dart:71:32) #5 _RouterState._processRouteInformation (package:flutter/src/widgets/router.dart:749:8) #6 _RouterState._handleRouteInformationProviderNotification (package:flutter/src/widgets/router.dart:767:5) #7 ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:433:24)
二次访问日志
I/flutter (14770): │ 👾 is ref token failure ? ........ true I/flutter (14770): │ 👾 is ref token failure ? ........ true
Main Widget Build方法
两种配置均无效:
方式一:
routeInformationParser: router.routeInformationParser, routeInformationProvider: router.routeInformationProvider, routerDelegate: router.routerDelegate,
方式二:
routerConfig: ref.watch(routerProvider),
具体代码:
@override Widget build(BuildContext outContext) { ref.watch(themeRProvider); return MaterialApp.router( routerConfig: ref.watch(routerProvider), // routeInformationParser: router.routeInformationParser, // routeInformationProvider: router.routeInformationProvider, // routerDelegate: router.routerDelegate, themeMode: themeProvider.themeMode == ThemeModeOption.light ? ThemeMode.light : themeProvider.themeMode == ThemeModeOption.dark ? ThemeMode.dark : ThemeMode.system, theme: lightTheme, darkTheme: darkTheme, // home: SplashPage(), ); }
RefreshTokenViewModel代码片段
enum RefreshTokenState { initial, loading, success, failure , networkFailure} class RefreshTokenViewModel extends ChangeNotifier { RefreshTokenState _refreshTokenViewModelState = RefreshTokenState.initial; RefreshTokenState get refreshTokenState => _refreshTokenViewModelState; setFailure(UserError error) { _refreshTokenViewModelState = RefreshTokenState.failure; notifyListeners(); } Future<void> refreshToken() async { var response = await refreshTokenRepository.refreshToken(); if (response is Failure) { setFailure(UserError(response.errorMessage)); } else { setSuccess(); } } }
解决方案
1. 同步初始状态,解决首次访问状态延迟问题
首次访问时authStateNotifier初始值为initial,但refreshTokenViewModelProvider已经处于failure状态,监听回调异步触发导致redirect逻辑先执行。修改Router Provider,初始化时直接同步最新状态:
final routerProvider = Provider<GoRouter>((ref) { final authState = ref.read(refreshTokenViewModelProvider); // 直接用当前状态初始化ValueNotifier,避免初始状态不一致 final authStateNotifier = ValueNotifier(authState.refreshTokenState); ref ..onDispose(authStateNotifier.dispose) ..listen(refreshTokenViewModelProvider, (_, value) { authStateNotifier.value = value.refreshTokenState; }); // 其余代码保持不变 });
2. 修复空路径异常,确保路由有效性
Location cannot be empty异常通常是因为redirect返回无效路径,确保/login路由已正确配置,并在redirect中增加兜底判断:
redirect: (context, state) { bool isRefreshTokenFailure = (authStateNotifier.value == RefreshTokenState.failure); if (isRefreshTokenFailure) { // 检查/login路由是否存在,避免返回无效路径 final hasLoginRoute = state.configuration.routes.any((r) => r.path == '/login'); return hasLoginRoute ? "/login" : "/"; // 兜底返回初始路径 } else { return null; } },
3. 优化token刷新时机,确保状态及时更新
在refreshTokenViewModelProvider初始化时自动触发token刷新,避免App启动后状态未及时更新:
final refreshTokenViewModelProvider = ChangeNotifierProvider((ref) { final viewModel = RefreshTokenViewModel(); // 初始化时立即执行token刷新 WidgetsBinding.instance.addPostFrameCallback((_) { viewModel.refreshToken(); }); return viewModel; });
4. 保持正确的路由配置方式
使用routerConfig: ref.watch(routerProvider)是正确的配置方式,若之前出现卡顿,检查themeRProvider等其他Provider是否存在耗时操作,避免阻塞UI初始化。
内容的提问来源于stack exchange,提问作者MRS
相关产品推荐
相关产品推荐

