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

Flutter中基于AsyncNotifier维护双认证状态的最佳实践咨询

优化Flutter+Riverpod双认证状态管理方案

当前实现的核心问题

  1. 单例模式违背Riverpod设计原则:Riverpod本身负责管理Notifier实例的生命周期,手动单例会导致无法正确获取ref、状态隔离失效,也是频繁重建的根源之一。
  2. 状态初始化逻辑缺失:build方法直接返回null,没有关联Firebase主认证状态,导致需要在全应用传递ID获取患者数据,冗余且易出错。
  3. 状态更新逻辑冲突:updatePatient中手动设置state = const AsyncLoading()和update方法的内置状态处理逻辑冲突,可能引发状态异常。
  4. 无状态持久化: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 04:33:11