Flutter中基于AsyncNotifier维护双认证状态的最佳实践咨询
优化Flutter+Riverpod双认证状态管理方案
当前实现的核心问题
- 单例模式违背Riverpod设计原则:Riverpod本身负责管理Notifier实例的生命周期,手动单例会导致无法正确获取
ref、状态隔离失效,也是频繁重建的根源之一。 - 状态初始化逻辑缺失:
build方法直接返回null,没有关联Firebase主认证状态,导致需要在全应用传递ID获取患者数据,冗余且易出错。 - 状态更新逻辑冲突:
updatePatient中手动设置state = const AsyncLoading()和update方法的内置状态处理逻辑冲突,可能引发状态异常。 - 无状态持久化:APP重启后患者状态丢失,需要重新操作。
优化步骤及代码实现
1. 移除单例,交由Riverpod管理实例
删除static PatientController patientController和自定义构造器,让Provider自动创建实例:
class PatientController extends AsyncNotifier<Patient?> { // 移除单例和自定义构造器 @override Future<Patient?> build() async { // 后续添加Firebase状态监听逻辑 return null; } // 其他方法保留,后续优化 } final patientControllerProvider = AsyncNotifierProvider<PatientController, Patient?>( () => PatientController(), // 直接返回新实例交由Riverpod管理 );
2. 关联Firebase Auth状态,自动同步Patient
在build方法中监听Firebase用户状态变化,当主认证用户登录时自动拉取对应Patient,无需手动传ID:
import 'package:firebase_auth/firebase_auth.dart'; @override Future<Patient?> build() async { // 监听Firebase主认证状态变化 final authState = ref.watch(authStateChangesProvider); final firebaseUser = authState.value; if (firebaseUser == null) { return null; // 主用户未登录,Patient状态置空 } // 主用户已登录,自动拉取关联的Patient数据 return AsyncValue.guard(() async { return ref.read(repositoryProvider).getPatientByUid(firebaseUser.uid); // 需在repository中新增根据Firebase UID获取Patient的方法 }); } // 提前定义Firebase Auth状态监听Provider final authStateChangesProvider = StreamProvider<User?>((ref) { return FirebaseAuth.instance.authStateChanges(); });
3. 修复状态更新逻辑
updatePatient中去掉手动设置AsyncLoading,利用AsyncValue.guard统一处理异步状态:
Future<void> updatePatient({ required String firstName, required String lastName, required DateTime birthday, }) async { if (state.value == null) return; state = const AsyncLoading(); state = await AsyncValue.guard(() async { final updatedPatient = state.value!.copyWith( firstName: firstName, lastName: lastName, birthday: birthday, ); await ref.read(repositoryProvider).updatePatient(patient: updatedPatient); return updatedPatient; }); }
4. 新增状态持久化(可选)
用shared_preferences存储Patient ID,APP启动时自动恢复状态:
import 'package:shared_preferences/shared_preferences.dart'; @override Future<Patient?> build() async { final prefs = await SharedPreferences.getInstance(); final patientId = prefs.getString('current_patient_id'); final authState = ref.watch(authStateChangesProvider); final firebaseUser = authState.value; if (firebaseUser == null || patientId == null) { prefs.remove('current_patient_id'); return null; } return AsyncValue.guard(() async { final patient = await ref.read(repositoryProvider).getPatientById(patientId); // 校验Patient是否关联当前Firebase用户 if (patient?.firebaseUid == firebaseUser.uid) return patient; prefs.remove('current_patient_id'); return null; }); } // 在loginPatient和createPatient成功后存储ID void logInPatient({...}) async { state = const AsyncLoading(); state = await AsyncValue.guard(() async { final patient = await ref.read(repositoryProvider).getPatient(...); final prefs = await SharedPreferences.getInstance(); await prefs.setString('current_patient_id', patient.id); return patient; }); }
5. 解耦路由逻辑
不在Notifier中直接调用路由,改为UI层监听状态变化后处理跳转:
// 移除Notifier中的路由代码 void createPatient({...}) async { state = const AsyncLoading(); state = await AsyncValue.guard(() async { final patient = await ref.read(repositoryProvider).createPatient(...); final prefs = await SharedPreferences.getInstance(); await prefs.setString('current_patient_id', patient.id); return patient; }); } // UI层监听状态变化处理跳转 ref.listen(patientControllerProvider, (previous, next) { if (next.hasValue && next.value != null && previous?.value == null) { ref.read(routerProvider).go('/doctor/patient/home'); } });
额外优化建议
- 拆分Provider:把Patient数据和操作逻辑拆分,比如用
StateProvider存储Patient ID,FutureProvider获取Patient详情,让职责更单一。 - 错误处理:在UI层监听
AsyncValue的错误状态,统一展示错误提示。 - 测试友好:移除单例后,更容易对Notifier进行单元测试,可通过
ProviderContainer注入模拟依赖。
内容的提问来源于stack exchange,提问作者Ferdi
相关产品推荐
相关产品推荐

