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

Flutter中AsyncNotifier结合ref.watch引发UI持续渲染及Firebase问题

一、ContractConfigProvider持续触发、UI重复渲染的原因及修复

核心原因

  1. 状态更新逻辑冗余:调用setContractType/setContractName时,先设置loading状态再立刻切换到data状态,两次状态变更都会触发UI重建。
  2. 模型未实现相等性判断:ContractConfigModel未重写==和hashCode,Riverpod无法识别属性相同的实例为同一状态,导致不必要的重建。
  3. build方法未限制重复执行:若authRepo的依赖发生变化(即使不影响用户数据),build会重新执行Firestore请求,重复更新状态。

修复步骤

1. 优化状态更新逻辑

去掉无意义的loading切换,直接更新数据状态:

void setContractType(String value) {
  final currentState = state.value;
  if (currentState == null) return;
  state = AsyncValue.data(
    ContractConfigModel(
      contractType: value,
      contractName: currentState.contractName,
    ),
  );
}

void setContractName(String value) {
  final currentState = state.value;
  if (currentState == null) return;
  state = AsyncValue.data(
    ContractConfigModel(
      contractType: currentState.contractType,
      contractName: value,
    ),
  );
}
2. 为模型添加相等性判断

重写==和hashCode,让Riverpod能正确识别状态是否变化:

class ContractConfigModel {
  String contractType;
  String contractName;

  ContractConfigModel({
    required this.contractType,
    required this.contractName,
  });

  ContractConfigModel.empty()
      : contractType = "",
        contractName = "";

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is ContractConfigModel &&
          runtimeType == other.runtimeType &&
          contractType == other.contractType &&
          contractName == other.contractName;

  @override
  int get hashCode => contractType.hashCode ^ contractName.hashCode;
}
3. 限制build方法重复执行

通过监听authRepo的user状态,仅在用户变化时重新请求Firestore:

@override
FutureOr<ContractConfigModel> build() async {
  _authRepository = ref.read(authRepo);
  // 仅当user变化时,重新执行后续逻辑
  final user = await ref.watch(authRepo.select((repo) => repo.user).future);
  if (user == null) return ContractConfigModel.empty();

  final userId = user.uid;
  final adminData = await _authRepository.getAdminProfile(userId);
  final adminProfile = AdminProfileModel.fromJson(adminData!);

  return adminProfile.master
      ? ContractConfigModel(
          contractType: adminProfile.contractType,
          contractName: adminProfile.region,
        )
      : ContractConfigModel(
          contractType: adminProfile.contractType,
          contractName: "${adminProfile.region} ${adminProfile.smallRegion}",
        );
}

二、Firebase初始化错误的解决

错误原因

应用启动时未完成Firebase初始化,路由的redirect逻辑(或其他代码)就提前访问了Firebase服务,导致抛出[core/no-app]异常。

修复步骤

1. 在main函数中优先初始化Firebase

确保所有依赖Firebase的代码都在初始化完成后执行:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // 初始化Firebase,Web平台需添加options参数
  await Firebase.initializeApp(
    // options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(const ProviderScope(child: MyApp()));
}
2. 路由逻辑等待Firebase初始化完成

创建Provider监听Firebase初始化状态,确保redirect逻辑在初始化完成后执行:

final firebaseInitializedProvider = FutureProvider<bool>((ref) async {
  await Firebase.initializeApp();
  return true;
});

在GoRouter的redirect中添加等待逻辑:

final router = GoRouter(
  redirect: (context, state) async {
    final initialized = await ref.watch(firebaseInitializedProvider.future);
    if (!initialized) return null; // 等待初始化完成

    // 后续的路由跳转逻辑
  },
  routes: [...],
);

内容的提问来源于stack exchange,提问作者Hyejung

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 13:08:06