Flutter Riverpod:如何在ref.listen()中调用Navigator避免重复跳转?
解决Riverpod监听认证状态重复跳转首页问题
问题场景
使用Riverpod的AuthStateNotifier管理登录状态,在登录页面通过ref.listen监听状态变化,期望认证通过时跳转到首页,但每次Widget重建都会触发跳转,导致重复进入首页。
相关代码如下:
Auth状态定义
// Auth state union class @freezed class AuthState with _$AuthState { const factory AuthState.initial() = _Initial; const factory AuthState.loading() = _Loading; const factory AuthState.unauthenticated({required String message}) = _UnAuthentication; const factory AuthState.authenticated({required User user}) = _Authenticated; }
Auth状态Provider
// Auth state provider: @Riverpod(keepAlive: true) class AuthStateNotifer extends _$AuthStateNotifer { @override AuthState build() { return const AuthState.initial(); } Future<void> login({required String email, required String password}) async { state = const AuthState.loading(); final response = await ref.watch(authServiceProvider).login(email: email, password: password); state = response.fold( (error) => AuthState.unauthenticated(message: error), (user) => AuthState.authenticated(user: user), ); } Future<void> signup(RegistrationData registrationData) async { state = const AuthState.loading(); final response = await ref.watch(authServiceProvider).signup(registrationData); state = response.fold( (error) => AuthState.unauthenticated(message: error), (user) => AuthState.authenticated(user: user), ); } void checkIfLoggedIn() { final response = ref.watch(authServiceProvider).checkIfLoggedIn(); state = response.fold( () => const AuthState.unauthenticated(message: "User not logged in"), (user) => AuthState.authenticated(user: user), ); } Future<void> signout() async { await ref.watch(authServiceProvider).signout(); Future.delayed(const Duration(milliseconds: 500), () { ref.invalidateSelf(); }); } }
登录页面监听逻辑
@override Widget build(BuildContext context) { ref.listen( authStateNotiferProvider, (previous, next) { next.maybeWhen( orElse: () => null, authenticated: (user) => Navigator.of(context).pushReplacementNamed(HomePage.route), unauthenticated: (message) { showError(message); }, ); }, ); ///... }
解决方案
1. 对比前后状态,仅在状态变更时触发操作
当前监听逻辑没有判断状态的变化过程,只要当前状态是authenticated就会触发跳转,包括Widget重建时状态未发生变化的情况。修改监听回调,对比previous和next状态,仅当状态从非认证变为认证时才执行跳转:
ref.listen( authStateNotiferProvider, (previous, next) { next.maybeWhen( authenticated: (user) { // 检查之前的状态是否为非认证状态 final wasPreviouslyAuthenticated = previous?.maybeWhen( authenticated: (_) => true, orElse: () => false, ) ?? false; // 仅当之前未认证,现在变为认证时才跳转 if (!wasPreviouslyAuthenticated) { Navigator.of(context).pushReplacementNamed(HomePage.route); } }, unauthenticated: (message) { // 同样避免重复弹出错误提示 final wasPreviouslyUnauthenticated = previous?.maybeWhen( unauthenticated: (_) => true, orElse: () => false, ) ?? false; if (!wasPreviouslyUnauthenticated) { showError(message); } }, orElse: () => null, ); }, );
2. 避免重复触发checkIfLoggedIn
如果登录页面在build方法中调用了checkIfLoggedIn,每次Widget重建都会重新设置状态,导致监听触发。将checkIfLoggedIn的调用移到initState中,确保只在页面初始化时执行一次:
@override void initState() { super.initState(); // 页面渲染完成后检查登录状态 WidgetsBinding.instance.addPostFrameCallback((_) { ref.read(authStateNotiferProvider.notifier).checkIfLoggedIn(); }); }
3. 优化Signout后的状态处理
signout方法中使用ref.invalidateSelf()会让Provider重建回到initial状态,这本身没问题,但可以改为直接设置状态为unauthenticated,避免依赖Provider重建带来的不确定性:
Future<void> signout() async { await ref.watch(authServiceProvider).signout(); state = const AuthState.unauthenticated(message: "已退出登录"); }
内容的提问来源于stack exchange,提问作者Kulpas
相关产品推荐
相关产品推荐

