PAYATHON 2026

Riverpod 登出后如何阻止 AsyncNotifier 重新请求用户资料?

数据阿明

结论

登出时不要调用 ref.invalidate(profileDetailsProvider)。首页仍通过 ref.watch(profileDetailsProvider) 监听这个 Provider。失效后,Provider 会重新执行 build(),并调用 fetchProfileDetails()。此时 token 已清除,请求可能返回 401。

登出时可以将资料状态设为 null,保留 Provider 实例。登录成功并写入 token 后,再使资料 Provider 失效,让它加载新用户的资料。

调整资料 Notifier

给 ProfileDetailsNotifier 添加一个清理方法:

void clear() {
  state = const AsyncData(null);
}

本地会话清除后,在登出流程中调用 clear():

Future<void> logout() async {
  try {
    state = const AsyncLoading();

    await _repo.logout();
    final sessionController = ref.read(sessionControllerProvider);
    await sessionController.logout();

    ref.read(profileDetailsProvider.notifier).clear();
    ref.invalidate(assignedVehicleProvider);
    ref.invalidate(homeControllerProvider);
    ref.invalidate(authStateProvider);

    state = const AsyncData(true);
  } catch (e, s) {
    state = AsyncError(e, s);
  }
}

clear() 只会更新当前资料状态,不会重新执行 build()。即使首页仍在监听 Provider,也会收到 AsyncData(null),并由现有的 data == null 分支显示错误或空状态组件。

在登录成功后重新加载资料

确认新 token 已写入后,再使资料 Provider 失效。这样即使路由 Shell 仍保留首页,新会话也会重新加载资料:

await _storage.setValue(AppConstant.ACCESS_TOKEN, accessToken);
await _storage.setValue(AppConstant.REFRESH_TOKEN, refreshToken);

ref.invalidate(authStateProvider);
ref.invalidate(profileDetailsProvider);

state = const AsyncData(true);

登录和登出的状态更新最好由同一个认证状态来源负责。这样 Provider 会在相应的会话状态变更后失效并重新加载。切换到认证路由不一定会卸载首页;使用 StatefulShellRoute 等会保留分支状态的路由结构时,原页面可能仍在监听 Provider。

注意已有请求尚未完成的情况

以上修改可以避免登出时由 ref.invalidate(profileDetailsProvider) 触发新请求。登出前已经发出的资料请求仍可能在登出后返回。若需要处理这类竞态,可以在请求完成后检查当前会话是否仍有效,再决定是否采用结果;也可以为会话设置递增标识,在登出时更新标识,以丢弃旧会话请求的结果。

refresh() 也会直接请求资料。应确保只有登录状态下才能触发,例如在方法内检查当前认证状态。页面跳转后按钮所在页面仍可能处于挂载状态,因此不能只依赖页面已经跳转。

备注:内容仅供参考。