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

Flutter中初始化耗时的ViewModel如何避免重复调用冲突?

Flutter Web预加载月度数据时的状态冲突问题

我的Flutter Web项目包含A、B、C三个菜单,B菜单下有“周”和“月”子菜单。点击“月”子菜单时需从Firestore获取大量数据处理后渲染UI,但Firestore获取月度数据速度较慢,因此我希望在进入A菜单时就预加载MonthRankingViewModel,以缩短用户等待时间。

我已在A页面的initState()中调用weekRankingProvider和monthRankingProvider,周数据加载正常,但点击“月”子菜单时出现错误。推测原因是MonthRankingViewModel的build方法尚未执行完成,就再次调用了updateUserScore方法,导致状态冲突。

问题根源

  • build方法异步逻辑失控:build内调用ref.watch(contractConfigProvider).when()的异步逻辑还在执行时,后面直接设置state = const AsyncValue.data([])并返回空列表,导致预加载逻辑被打断,同时后续updateUserScore又修改状态,引发冲突。
  • updateUserScore状态判断错误:方法开头先将state设为loading,紧接着判断state.value!.isEmpty,此时state处于loading状态,state.value为null,直接访问isEmpty会触发空指针异常。
  • 重复执行数据处理:预加载时build已调用updateUserScore,点击子菜单时再次调用,导致两次异步操作同时修改状态。

修复方案

1. 修正build方法的异步逻辑

让build方法等待contractConfigProvider的数据加载完成,确保预加载逻辑完整执行后再更新状态,避免提前设置空状态打断流程。

2. 优化状态管理与数据缓存

  • 新增内部缓存变量存储已加载完成的月度数据,不再依赖state.value判断是否已加载。
  • 拆分数据处理逻辑,避免重复代码,同时增加加载互斥判断,防止重复执行数据计算。

3. 页面端复用预加载数据

点击“月”子菜单时,直接从ViewModel获取预加载好的数据,不再重复调用updateUserScore触发数据处理。

修改后的代码

MonthRankingViewModel

class MonthRankingViewModel extends AsyncNotifier<List<UserModel?>> {
  late RankingRepository _rankingRepository;
  DateTime now = DateTime.now();
  late DateTime firstDateOfMonth;
  // 新增缓存变量,存储已加载完成的月度数据
  List<UserModel?>? _cachedMonthData;

  @override
  FutureOr<List<UserModel?>> build() async {
    _rankingRepository = RankingRepository();
    now = DateTime.now();
    firstDateOfMonth = DateTime(now.year, now.month, 1, 0, 0);

    // 等待contractConfigProvider的数据加载完成
    final contractConfig = await ref.watch(contractConfigProvider.future);
    final getUserContractType = contractConfig.contractType;
    final getUserContractName = contractConfig.contractName;

    List<UserModel?> userDataList;
    if (getUserContractType == "지역") {
      userDataList = await ref.watch(userRepo).getRegionUserData(getUserContractName);
    } else if (getUserContractType == "기관") {
      userDataList = await ref.watch(userRepo).getCommunityUserData(getUserContractName);
    } else if (getUserContractType == "마스터" || getUserContractType == "전체") {
      userDataList = await ref.watch(userRepo).getAllUserData();
    } else {
      userDataList = [];
    }

    // 处理数据并更新状态,同时缓存结果
    _cachedMonthData = await _calculateAndSortUserScores(userDataList, "종합 점수");
    state = AsyncValue.data(_cachedMonthData!);
    return _cachedMonthData!;
  }

  // 拆分数据处理逻辑,避免重复代码
  Future<List<UserModel?>> _calculateAndSortUserScores(List<UserModel?> users, String sortOrder) async {
    final updateScoreList = <UserModel?>[];
    for (final user in users) {
      if (user != null) {
        final scoreUser = await calculateUserScore(user);
        updateScoreList.add(scoreUser);
      }
    }

    updateScoreList.sort((a, b) => b!.totalScore!.compareTo(a!.totalScore!));
    return ref.read(userProvider.notifier).indexUserModel(sortOrder, updateScoreList);
  }

  // 对外提供获取预加载数据的方法,避免重复处理
  Future<List<UserModel?>> getMonthData(String sortOrder) async {
    // 如果缓存存在,直接返回;如果还在加载,等待build完成
    if (_cachedMonthData != null) {
      return ref.read(userProvider.notifier).indexUserModel(sortOrder, _cachedMonthData!);
    } else {
      // 等待build方法完成加载
      await future;
      return ref.read(userProvider.notifier).indexUserModel(sortOrder, _cachedMonthData!);
    }
  }

  Future<UserModel?> calculateUserScore(UserModel? user) async {
    final String userId = user!.userId;
    final results = await Future.wait([
      _rankingRepository.getUserStepScores(userId, firstDateOfMonth, now),
      _rankingRepository.getUserDiaryScores(userId, firstDateOfMonth, now),
      _rankingRepository.getUserCommentScores(userId, firstDateOfMonth, now),
    ]);

    final userTotalScore = results[0] + results[1] + results[2];
    return user.copyWith(
      totalScore: userTotalScore,
      stepScore: results[0],
      diaryScore: results[1],
      commentScore: results[2],
    );
  }
}

final monthRankingProvider = AsyncNotifierProvider<MonthRankingViewModel, List<UserModel?>>(
  () => MonthRankingViewModel(),
);

A页面initState(无需修改)

@override
void initState() {
  super.initState();
  ref.read(weekRankingProvider);
  ref.read(monthRankingProvider);
}

子菜单点击逻辑修改

void updateOrderPeriod(String value) async {
  setState(() {
    _sortOrderPeriod = value;
    loadingFinished = false;
  });
  switch (value) {
    case "이번달":
      // 直接获取预加载好的数据,不再调用updateUserScore
      final monthList = await ref.read(monthRankingProvider.notifier).getMonthData(_sortOrderStandard);
      setState(() {
        _userDataList = monthList;
        loadingFinished = true;
      });
      break;
    case "이번주":
      final weekList = await ref.read(weekRankingProvider.notifier).getWeekData(_sortOrderStandard);
      setState(() {
        _userDataList = weekList;
        loadingFinished = true;
      });
      break;
  }
}

内容的提问来源于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 03:08:26