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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 07:52:51