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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 17:53:16