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() 也会直接请求资料。应确保只有登录状态下才能触发,例如在方法内检查当前认证状态。页面跳转后按钮所在页面仍可能处于挂载状态,因此不能只依赖页面已经跳转。
备注:内容仅供参考。
