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
相关产品推荐
相关产品推荐

