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

在Flutter中结合StreamBuilder与GoRouter持久化用户状态的问询

用GoRouter结合StreamBuilder实现Flutter用户认证状态持久化问题

问题概述

原本通过Flutter默认导航+StreamBuilder实现用户认证状态持久化,现在计划替换为GoRouter,想明确两个问题:

  1. 是否可以结合StreamBuilder与GoRouter实现该功能?
  2. 结合时在GoRouter代码中的具体使用位置?
    同时当前的RouteNotifier类存在User类型无isEmpty属性的错误,需要修复。

原有默认导航实现代码

home: StreamBuilder(
       stream: AuthMethods().authChanges,
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
        return const Center(
          child: CircularProgressIndicator(),
           );
         }

         if (snapshot.hasData) {
           return const TabsScreen();
        }

        return const AuthScreen();
       }),
);
}

当前GoRouter初始代码

GoRouter routeConstruct() {
  return GoRouter(
    routes: <RouteBase>[
      GoRoute(
          path: '/',
          builder: (BuildContext context, GoRouterState state) {
            return const PublicScreen();
          },
          routes: <RouteBase>[
            GoRoute(
              path: Events_Screen,
              builder: (BuildContext context, GoRouterState state) {
                return const EventsScreen();
              },
            ),
            GoRoute(
              path: "$EventDetail_Screen/:eventId",
              builder: (BuildContext context, GoRouterState state) {
                return EventDetailScreen(eventId: state.params['eventId']!);
              },
            ),
            ...
          ]),
    ],
  );
}

AuthMethods类代码

class AuthMethods {
  final FirebaseAuth _auth = FirebaseAuth.instance;
  final FirebaseFirestore _firestore = FirebaseFirestore.instance;

  Stream<User?> get authChanges => _auth.authStateChanges();
// --> 使用了!运算符
  User get user => _auth.currentUser!;

  Future<bool> signInWithGoogle(BuildContext context) async {
    bool res = false;

    try {
      final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
      final GoogleSignInAuthentication? googleAuth =
          await googleUser?.authentication;

      final credential = GoogleAuthProvider.credential(
          accessToken: googleAuth?.accessToken, idToken: googleAuth?.idToken);

      UserCredential userCredential =
          await _auth.signInWithCredential(credential);

      User? user = userCredential.user;

      if (user != null) {
        if (userCredential.additionalUserInfo!.isNewUser) {
          await _firestore.collection('users').doc(user.uid).set({
            'username': user.displayName,
            'uid': user.uid,
            'profilePhoto': user.photoURL,
          });
        }
        res = true;
      }
    } on FirebaseAuthException catch (e) {
      showSnackBar(context, e.message!);
      res = false;
    }
    return res;
  }
}

RouteNotifier类代码(存在错误)

class RouterNotifier extends ChangeNotifier {
  RouterNotifier(this._authMethods) {
    _authMethods.authChanges.listen((event) {
      notifyListeners();
    });
  }

  final AuthMethods _authMethods;

  String? redirect(BuildContext context, GoRouterState state) {
    final onLoginPage = state.location == '/$AuthScreen';
    final onHomePage = state.location == '/$Tabs_Screen';

// --> 错误:类型'User'没有定义'isEmpty' getter
// --> User get user => _auth.currentUser!; 使用了!运算符
    if (_authMethods.user.isEmpty && onHomePage) {
      return '/$AuthScreen';
    }

// --> 错误:类型'User'没有定义'isNotEmpty' getter
    if (_authMethods.currentUser.isNotEmpty && onLoginPage) {
      return '/$Tabs_Screen';
    }

    return null;
  }
}

集成后的router.dart代码

GoRouter routeConstruct() {
  return GoRouter(
    refreshListenable: router,

    routes: <RouteBase>[
      GoRoute(
          path: '/',
          builder: (BuildContext context, GoRouterState state) {
            return const PublicScreen();
          },
          routes: <RouteBase>[
            GoRoute(
              path: Events_Screen,
              builder: (BuildContext context, GoRouterState state) {
                return const EventsScreen();
              },
            ),
            GoRoute(
              path: "$EventDetail_Screen/:eventId",
              builder: (BuildContext context, GoRouterState state) {
                return EventDetailScreen(eventId: state.params['eventId']!);
              },
            ),
          ]),
    ],
  );
}

问题解答

1. 能否结合StreamBuilder与GoRouter实现认证状态持久化?

完全可以。GoRouter支持通过refreshListenable监听状态变化,同时也能在路由的全局builder或页面内部结合StreamBuilder处理认证加载状态。更推荐的方式是用GoRouter的redirect+refreshListenable机制配合认证Stream实现自动路由跳转,再通过全局StreamBuilder统一处理加载状态。

2. 具体实现方式与代码位置

首先修复RouteNotifier的错误

User类型没有isEmpty/isNotEmpty属性,判断用户是否登录应直接检查currentUser是否为null,同时要避免滥用!运算符防止空指针:

修改后的RouteNotifier:

class RouterNotifier extends ChangeNotifier {
  RouterNotifier(this._authMethods) {
    // 监听认证状态变化,触发路由刷新
    _authChangesSubscription = _authMethods.authChanges.listen((_) {
      notifyListeners();
    });
  }

  final AuthMethods _authMethods;
  late final StreamSubscription<User?> _authChangesSubscription;

  // 判断用户是否已登录
  bool get isAuthenticated => _authMethods._auth.currentUser != null;

  String? redirect(BuildContext context, GoRouterState state) {
    final isOnAuthScreen = state.location == '/$AuthScreen';
    final isOnHomeScreen = state.location == '/$Tabs_Screen';

    // 未登录但在首页,跳转到登录页
    if (!isAuthenticated && isOnHomeScreen) {
      return '/$AuthScreen';
    }

    // 已登录但在登录页,跳转到首页
    if (isAuthenticated && isOnAuthScreen) {
      return '/$Tabs_Screen';
    }

    // 其他情况不跳转
    return null;
  }

  @override
  void dispose() {
    _authChangesSubscription.cancel();
    super.dispose();
  }
}

同时修改AuthMethods的user getter,增加空安全:

// 替换原来的User get user => _auth.currentUser!;
User? get user => _auth.currentUser;

结合StreamBuilder处理加载状态

在GoRouter的全局builder中使用StreamBuilder统一处理认证状态的等待过程,避免页面闪烁:

更新后的router.dart:

// 先初始化RouterNotifier
final routerNotifier = RouterNotifier(AuthMethods());

GoRouter routeConstruct() {
  return GoRouter(
    refreshListenable: routerNotifier,
    // 处理路由跳转逻辑
    redirect: routerNotifier.redirect,
    // 配置错误页面(可选)
    errorBuilder: (context, state) => const Scaffold(body: Center(child: Text('页面出错'))),
    routes: <RouteBase>[
      // 登录页路由
      GoRoute(
        path: '/$AuthScreen',
        builder: (context, state) => const AuthScreen(),
      ),
      // 首页路由
      GoRoute(
        path: '/$Tabs_Screen',
        builder: (context, state) => const TabsScreen(),
      ),
      // 公共页面路由(无需登录即可访问)
      GoRoute(
        path: '/',
        builder: (BuildContext context, GoRouterState state) {
          return const PublicScreen();
        },
        routes: <RouteBase>[
          GoRoute(
            path: Events_Screen,
            builder: (context, state) => const EventsScreen(),
          ),
          GoRoute(
            path: "$EventDetail_Screen/:eventId",
            builder: (context, state) => EventDetailScreen(eventId: state.params['eventId']!),
          ),
        ],
      ),
    ],
    // 初始加载位置
    initialLocation: '/',
    // 全局builder处理认证加载状态
    builder: (context, state, child) {
      return StreamBuilder<User?>(
        stream: AuthMethods().authChanges,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          }
          // 认证状态确定后返回对应页面
          return child!;
        },
      );
    },
  );
}

关键逻辑说明

  • refreshListenable: 传入RouterNotifier,当认证状态变化时,GoRouter自动调用redirect方法刷新路由。
  • redirect: 由RouterNotifier提供,根据用户登录状态和当前页面决定是否跳转。
  • 全局builder: 用StreamBuilder包裹所有页面,统一处理认证状态的等待过程,展示加载指示器。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 01:36:21