Flutter中BlocListener未监听AuthCubit状态变更问题求助
问题描述
在Widget树顶部配置了lazy: false的BlocProvider(对应AuthCubit),目的是立即监听认证状态变更。但登录页使用BlocListener时出现异常:Firebase Auth先返回user为null(对应isUserLoggedIn为false),随后返回有效FirebaseUser(对应isUserLoggedIn为true),导致BlocListener未触发预期的页面跳转。尝试过listenWhen属性、WidgetsBinding.instance.addPostFrameCallback均未解决。重启应用后,用户状态直接显示为非null的FirebaseUser。
相关代码实现
AuthCubit部分
late StreamSubscription<AuthUserModel>? _authUserSubscription; _authUserSubscription = _authService.authStateChanges.listen(_listenAuthStateChangesStream); Future<void> _listenAuthStateChangesStream(AuthUserModel authUser) async { emit(state.copyWith(isInProgress: true)); if (AuthUserModel.empty() == authUser) { emit( state.copyWith( authUser: authUser, isUserLoggedIn: false, isInProgress: false, ), ); } else { await _chatService.connectTheCurrentUser(); emit( state.copyWith( authUser: authUser, isUserLoggedIn: true, isInProgress: false, ), ); } }
基础设施层部分
@override Stream<AuthUserModel> get authStateChanges { return _firebaseAuth.authStateChanges().map( (User? user) { if (user == null) { return AuthUserModel.empty(); } else { return user.toDomain(); } }, ); }
Widget树顶部BlocProvider配置
BlocProvider( lazy: false, create: (context) => getIt<AuthCubit>(), child: Listener( onPointerUp: (_) { if (Platform.isIOS) { final FocusScopeNode currentFocus = FocusScope.of(context); if (!currentFocus.hasPrimaryFocus && currentFocus.focusedChild != null) { FocusManager.instance.primaryFocus!.unfocus(); } } }, child: MaterialApp.router(...
登录页BlocListener代码
@override Widget build(BuildContext context) { return BlocListener<AuthCubit, AuthState>( listener: (context, state) { if (state.isUserLoggedIn) { context.go(context.namedLocation("channels_page")); } else { context.go(context.namedLocation("sign_in_page")); } }, child: const Scaffold( body: Center( child: CustomProgressIndicator( progressIndicatorColor: Colors.black, ), ), ), ); }
解决方案
1. 精准控制BlocListener的触发条件
通过listenWhen过滤无效状态变更,只在isUserLoggedIn状态实际切换且当前非加载状态时触发跳转,避免连续状态变更的干扰:
@override Widget build(BuildContext context) { return BlocListener<AuthCubit, AuthState>( listenWhen: (previous, current) { // 仅当登录状态发生变化,且当前状态不是加载中时执行监听逻辑 return previous.isUserLoggedIn != current.isUserLoggedIn && !current.isInProgress; }, listener: (context, state) { if (state.isUserLoggedIn) { context.go(context.namedLocation("channels_page")); } else { context.go(context.namedLocation("sign_in_page")); } }, child: const Scaffold( body: Center( child: CustomProgressIndicator( progressIndicatorColor: Colors.black, ), ), ), ); }
2. 优化AuthCubit的状态发射逻辑
避免连续发射加载状态和最终状态,调整为仅在需要时发射加载状态,确保状态变更的顺序清晰:
Future<void> _listenAuthStateChangesStream(AuthUserModel authUser) async { if (AuthUserModel.empty() == authUser) { emit( state.copyWith( authUser: authUser, isUserLoggedIn: false, isInProgress: false, ), ); } else { // 仅在处理用户连接时发射加载状态 emit(state.copyWith(isInProgress: true)); await _chatService.connectTheCurrentUser(); emit( state.copyWith( authUser: authUser, isUserLoggedIn: true, isInProgress: false, ), ); } }
3. 修正Firebase Auth状态流的初始值
给状态流添加初始的当前用户值,避免首次订阅时先返回null再返回有效用户的问题:
@override Stream<AuthUserModel> get authStateChanges { return _firebaseAuth.authStateChanges() .startWith(_firebaseAuth.currentUser) // 初始发送已缓存的当前用户 .map((User? user) { if (user == null) { return AuthUserModel.empty(); } else { return user.toDomain(); } }); }
核心要点
- 过滤无效状态:通过
listenWhen跳过加载状态和无意义的状态重复,只处理最终认证结果。 - 避免状态冲突:确保状态发射的顺序合理,连续快速的状态变更可能导致跳转逻辑被覆盖。
- 利用缓存用户:Firebase会缓存当前用户,初始发送缓存值可以减少状态流的波动。
内容的提问来源于stack exchange,提问作者Alper Efe Sahin
相关产品推荐
相关产品推荐

