使用Provider监听Firebase Auth状态时登出跳转失效问题
问题:Firebase Auth状态监听失效,登出后无法自动跳转登录页
我通过Provider监听Firebase Auth的状态变化,期望实现用户登出时自动跳转到登录页面。目前登录流程正常:用户登录后可自动跳转至首页,但执行登出操作后,状态变化无法触发页面跳转到登录页。
相关实现代码如下:
Provider中的Auth监听方法
Future<void> initFirebaseAuth(BuildContext context) async { auth.authStateChanges().listen((User? user) { Logger().w('userChanges: $user'); if (user == null) { Logger().w('User is currently signed out!'); if (!context.mounted) return; context.go(SignInScreen.route); } else { Logger().w('User is signed in!'); if (!context.mounted) return; context.go(HomeScreen.route); } }); }
启动页(SplashScreen)调用Provider的代码
class SplashScreen extends StatefulWidget { const SplashScreen({super.key}); static const route = '/splash'; @override State<SplashScreen> createState() => _SplashScreenState(); } class _SplashScreenState extends State<SplashScreen> { @override void initState() { super.initState(); context.read<AppProvider>().initFirebaseAuth(context); } @override Widget build(BuildContext context) { return const Scaffold( body: Center( child: Text('SplashScreen'), ), ); } }
问题原因
核心问题在于你传入Provider的是SplashScreen的context:
SplashScreen仅在App启动时短暂显示,之后会被导航到其他页面,此时SplashScreen的context会被标记为!mounted。当用户登出触发Auth状态变化时,代码中if (!context.mounted) return会直接终止跳转逻辑,导致无法跳转到登录页。
解决方案
方案1:在Provider中维护Auth状态,通过全局监听触发跳转
将用户状态维护在Provider内部,通过notifyListeners()通知UI更新,再在全局路由或父组件中监听状态变化执行跳转,避免依赖已销毁页面的context。
修改AppProvider代码
class AppProvider extends ChangeNotifier { final FirebaseAuth _auth = FirebaseAuth.instance; User? _currentUser; User? get currentUser => _currentUser; void initFirebaseAuth() { _auth.authStateChanges().listen((User? user) { Logger().w('userChanges: $user'); _currentUser = user; // 通知UI状态更新 notifyListeners(); }); } }
在全局路由中监听状态(以GoRouter为例)
final GoRouter router = GoRouter( routes: [ GoRoute(path: '/splash', builder: (_, __) => const SplashScreen()), GoRoute(path: '/signin', builder: (_, __) => const SignInScreen()), GoRoute(path: '/home', builder: (_, __) => const HomeScreen()), ], // 根据Auth状态自动重定向 redirect: (context, state) { final user = context.read<AppProvider>().currentUser; final isSignInPage = state.location == SignInScreen.route; final isSplashPage = state.location == SplashScreen.route; // 用户未登录:跳转到登录页(当前已是登录页则不跳转) if (user == null) { return isSignInPage ? null : SignInScreen.route; } // 用户已登录:跳转到首页(当前已是首页或启动页则不跳转) else { if (isSplashPage || isSignInPage) { return HomeScreen.route; } return null; } }, // 监听Provider状态变化,触发重定向 refreshListenable: GoRouterRefreshStream(FirebaseAuth.instance.authStateChanges()), );
修改SplashScreen的initState
无需再传入context,仅初始化监听即可:
@override void initState() { super.initState(); context.read<AppProvider>().initFirebaseAuth(); // 启动页可保留短暂展示逻辑 Future.delayed(const Duration(seconds: 1)); }
方案2:使用全局导航键
定义全局导航键,直接通过键获取导航上下文,避免依赖页面context:
// main.dart中定义全局导航键 final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); // MaterialApp中配置 MaterialApp( navigatorKey: navigatorKey, // ...其他配置 ) // 修改Provider中的跳转逻辑 Future<void> initFirebaseAuth() async { auth.authStateChanges().listen((User? user) { Logger().w('userChanges: $user'); if (user == null) { Logger().w('User is currently signed out!'); navigatorKey.currentState?.pushNamed(SignInScreen.route); } else { Logger().w('User is signed in!'); navigatorKey.currentState?.pushNamed(HomeScreen.route); } }); }
内容的提问来源于stack exchange,提问作者CCP
相关产品推荐
相关产品推荐

