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

使用go_router异步重定向时Flutter组件不重建的问题排查

基于Firebase认证状态的GoRouter重定向不触发问题

我之前已经通过Bloc Listener实现了基于Firebase认证状态的登录页到首页的导航,确认认证逻辑正常。现在按照Flutter官方推荐改用GoRouter实现,但遇到问题:当认证流产生新事件时,尽管AuthChangeNotifier已经触发了notifyListeners(),GoRouter的redirect方法仍不会被调用,组件也不会重建。

涉及的四个组件:

  • go_router(导航器)
  • Firebase(登录提供方)
  • AuthChangeNotifier(参照官方示例实现)
  • AuthNotifier(参照官方示例实现)

我清楚自己的实现和官方示例有两处差异:

  1. 在GoRouter的redirect方法中直接使用Firebase客户端,而非通过AuthNotifier.of(context)获取状态
  2. AuthNotifier并非作为根节点加入组件树,而是在MultipleBlocProviders和MultipleRepositoryProvider之后

相关代码

ROUTER:

GoRouter(
      redirect: (context, state) async {
        // 与示例不同:直接使用Firebase客户端而非AuthNotifier
        final currentUser =
            await RepositoryProvider.of<auth_repo.FirebaseAuthenticator>(
                    context,
                    listen: false)
                .currentUserStream()
                .first;

        // 此处能正确获取到用户
        bool? isSignedIn = await currentUser?.isSignedIn();
        final bool loggingIn = state.matchedLocation == '/login';

        if (loggingIn || isSignedIn == null || isSignedIn == false) {
          return '/login';
        }

        if (isSignedIn) {
          return '/home';
        }

        return null;
      },
      routes: <RouteBase>[
        GoRoute(
          path: '/home',
          builder: (BuildContext context, GoRouterState state) {
            return const HomePage();
          },
          routes: <RouteBase>[
            GoRoute(
              path: 'account',
              builder: (BuildContext context, GoRouterState state) {
                return AccountPage();
              },
            ),
          ],
        ),
        GoRoute(
          path: '/login',
          builder: (BuildContext context, GoRouterState state) {
            return const LoginPage();
          },
        ),
      ],
    );

AuthChangeNotifier

class AuthChangeNotifier extends ChangeNotifier {
  Stream<auth_repo.User> userStream;
  AuthChangeNotifier(this.userStream) {
    userStream.asBroadcastStream().listen((_) {
      notifyListeners(); // 此处会被调用
    });
  }
}

AuthNotifier

class AuthNotifier extends InheritedNotifier<AuthChangeNotifier> {
  AuthNotifier({
    Key? key,
    required auth_repo.AuthenticatorInterface authenticator,
    required Widget child,
  }) : super(
          key: key,
          notifier: AuthChangeNotifier(authenticator.currentUserStream()),
          child: child,
        );

  static Stream<auth_repo.User> of(BuildContext context) {
    final AuthChangeNotifier? result =
        context.dependOnInheritedWidgetOfExactType<AuthNotifier>()!.notifier;
    if (result == null) {
      throw FlutterError(
          'AuthNotifier.of() called with a context that does not contain an AuthNotifier.');
    }
    return result.userStream;
  }
}

AuthNotifier的使用

// 与示例不同:先实例化所有Bloc和Provider,再包裹AuthNotifier
    return _blocProvider(
      _repositoryProvider(
        AuthNotifier(authenticator: _authenticator, child: const MyApp()),
      ),
    );

问题原因与修复方案

核心问题

GoRouter的redirect方法要能响应认证状态变化,必须依赖AuthNotifier这个InheritedNotifier,或者通过refreshListenable绑定AuthChangeNotifier。你的当前实现中,redirect直接调用Firebase客户端获取流,但没有让GoRouter感知到这个流的状态变化,同时也没有依赖AuthNotifier,所以即使notifyListeners()被调用,GoRouter也不会重新执行redirect。

具体修复步骤

  1. 修改GoRouter的redirect逻辑,依赖AuthNotifier
    在redirect中调用AuthNotifier.of(context),这个方法内部的context.dependOnInheritedWidgetOfExactType会让GoRouter的上下文绑定AuthNotifier,当AuthChangeNotifier触发通知时,GoRouter会自动重新执行redirect。同时调整重定向逻辑的合理性:

    redirect: (context, state) async {
      // 通过AuthNotifier获取用户流,建立依赖关系
      final userStream = AuthNotifier.of(context);
      final currentUser = await userStream.first;
      bool? isSignedIn = await currentUser?.isSignedIn();
      final bool loggingIn = state.matchedLocation == '/login';
    
      // 未登录且不在登录页时,跳转到登录页
      if (isSignedIn == null || isSignedIn == false) {
        return loggingIn ? null : '/login';
      }
    
      // 已登录但在登录页时,跳转到首页
      if (isSignedIn && loggingIn) {
        return '/home';
      }
    
      // 其他情况不重定向
      return null;
    },
    
  2. 添加refreshListenable绑定(可选但更可靠)
    结合官方示例的做法,给GoRouter传入refreshListenable参数,直接绑定AuthChangeNotifier,确保状态变化时GoRouter主动刷新:

    // 在创建GoRouter前,先获取AuthChangeNotifier实例
    final authChangeNotifier = AuthChangeNotifier(_authenticator.currentUserStream());
    
    GoRouter(
      refreshListenable: authChangeNotifier,
      redirect: (context, state) async {
        final currentUser = await authChangeNotifier.userStream.first;
        bool? isSignedIn = await currentUser?.isSignedIn();
        final bool loggingIn = state.matchedLocation == '/login';
    
        if (isSignedIn == null || isSignedIn == false) {
          return loggingIn ? null : '/login';
        }
    
        if (isSignedIn && loggingIn) {
          return '/home';
        }
    
        return null;
      },
      // ... 路由配置
    )
    

    这种方式不需要依赖InheritedNotifier的上下文,更直接地让GoRouter监听状态变化。

  3. 确认AuthNotifier的组件树位置
    只要GoRouter所在的上下文能访问到AuthNotifier,当前的位置(在Bloc和RepositoryProvider之后)是没问题的。确保MyApp中的GoRouter是在AuthNotifier的子树内,即GoRouter的context可以调用AuthNotifier.of(context)获取到实例。

额外优化点

  • 当前AuthNotifier.of()返回的是Stream,你可以修改它返回AuthChangeNotifier实例,方便在redirect中直接获取状态,减少重复代码。
  • 避免在redirect中重复监听流,使用.first获取当前最新状态即可,因为refreshListenable或InheritedNotifier会触发redirect重新执行,每次都会获取最新状态。

内容的提问来源于stack exchange,提问作者Wiktoor

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 03:56:00