You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 22:41:08