修复优化引起的 demo bug

This commit is contained in:
Cody
2026-03-08 21:38:25 +08:00
parent 9610c455ec
commit 132349c410
2 changed files with 19 additions and 6 deletions

View File

@@ -59,6 +59,10 @@ class LoginViewModel extends _$LoginViewModel {
/// 仅用于演示路由守卫行为,正式 UI 就绪后删除此方法,改用 [login]。 /// 仅用于演示路由守卫行为,正式 UI 就绪后删除此方法,改用 [login]。
/// 正式 [login] 成功后同样需要调用 [AuthNotifier.login] 更新守卫状态。 /// 正式 [login] 成功后同样需要调用 [AuthNotifier.login] 更新守卫状态。
Future<void> demoLogin() async { Future<void> demoLogin() async {
// 防止连点重入:第一次调用未完成前忽略后续调用
if (state.isLoading) return;
state = state.copyWith(isLoading: true, error: null);
try { try {
final storageApi = ref.read(storageSdkProvider); final storageApi = ref.read(storageSdkProvider);
final storageLifeCycle = storageApi as StorageSdkLifecycle; final storageLifeCycle = storageApi as StorageSdkLifecycle;
@@ -72,11 +76,15 @@ class LoginViewModel extends _$LoginViewModel {
// 先完成 DB 操作,再标记登录状态(失败时不会误标为已登录) // 先完成 DB 操作,再标记登录状态(失败时不会误标为已登录)
await storageLifeCycle.openDatabase(user.uid); await storageLifeCycle.openDatabase(user.uid);
final userCompanion = UserDto.fromEntity(user).toCompanion(); final userCompanion = UserDto.fromEntity(user).toCompanion();
await storageApi.insert(userCompanion); await storageApi.insertOrReplace(userCompanion);
// 全部成功后再更新登录状态,触发路由守卫重定向 // 全部成功后再更新登录状态,触发路由守卫重定向
// 注意login() 触发导航后 provider 随即被 dispose之后不能再写 state
if (!ref.mounted) return;
ref.read(authNotifierProvider).login(); ref.read(authNotifierProvider).login();
} catch (e) { } catch (e) {
// 导航已发生时 provider 已被 dispose静默丢弃不再写 state
if (!ref.mounted) return;
state = state.copyWith(error: e.toString(), isLoading: false); state = state.copyWith(error: e.toString(), isLoading: false);
} }
} }
@@ -94,15 +102,19 @@ class LoginViewModel extends _$LoginViewModel {
.read(loginUseCaseProvider) .read(loginUseCaseProvider)
.execute(email: email, password: password); .execute(email: email, password: password);
if (!ref.mounted) return;
state = state.copyWith(user: user, isLoading: false); state = state.copyWith(user: user, isLoading: false);
} on FormatException catch (e) { } on FormatException catch (e) {
// 格式校验失败UseCase 层抛出) // 格式校验失败UseCase 层抛出)
if (!ref.mounted) return;
state = state.copyWith(error: e.message, isLoading: false); state = state.copyWith(error: e.message, isLoading: false);
} on ApiError catch (e) { } on ApiError catch (e) {
// 网络 / 服务端错误Repository → SDK 透传) // 网络 / 服务端错误Repository → SDK 透传)
if (!ref.mounted) return;
state = state.copyWith(error: e.displayMessage, isLoading: false); state = state.copyWith(error: e.displayMessage, isLoading: false);
} catch (e) { } catch (e) {
// 兜底:防止未预期的异常导致 isLoading 死锁 // 兜底:防止未预期的异常导致 isLoading 死锁
if (!ref.mounted) return;
state = state.copyWith(error: e.toString(), isLoading: false); state = state.copyWith(error: e.toString(), isLoading: false);
} }
} }

View File

@@ -15,13 +15,12 @@ class LoginPage extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
// ref.watch 保持 loginViewModelProvider 存活AutoDispose 需要至少一个监听者)
final state = ref.watch(loginViewModelProvider);
final s = context.styles; final s = context.styles;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(title: const Text('登录'), automaticallyImplyLeading: false),
title: const Text('登录'),
automaticallyImplyLeading: false,
),
body: Center( body: Center(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -34,7 +33,9 @@ class LoginPage extends ConsumerWidget {
), ),
const SizedBox(height: 32), const SizedBox(height: 32),
FilledButton( FilledButton(
onPressed: () => ref.read(loginViewModelProvider.notifier).demoLogin(), onPressed: state.isLoading
? null
: () => ref.read(loginViewModelProvider.notifier).demoLogin(),
child: const Text('登录'), child: const Text('登录'),
), ),
], ],