英文:
How to make ViewModel to be initialized with build() that takes too long in Flutter?
问题
以下是您要翻译的内容:
"I have 3 menus in my Flutter
web: A, B, C.
And in menu B, it has sub menu 'week' and 'month'.
When I click menu 'month', It gets several data from Firestore
and compile it and draw UI.
But the problem is that Firestore is very slow to get several data.
(Of course, month data is bigger than week data, so It takes much more time)
So If It start getting data when I click sub-menu 'month', It's too long to wait for loading finished.
My Idea is to start getting data when I access menu A, which means my loading start point moves forward in order to shorten loading time from user's point.
ViewModel
to extract month data is below:
class MonthRankingViewModel extends AsyncNotifier<List<UserModel?>> {
late RankingRepository _rankingRepository;
DateTime now = DateTime.now();
late DateTime firstDateOfMonth;
late List<UserModel?> monthUserList;
@override
FutureOr<List<UserModel?>> build() async {
_rankingRepository = RankingRepository();
DateTime now = DateTime.now();
firstDateOfMonth = DateTime(now.year, now.month, 1);
firstDateOfMonth = DateTime(firstDateOfMonth.year, firstDateOfMonth.month,
firstDateOfMonth.day, 0, 0);
ref.watch(contractConfigProvider).when(
data: (data) async {
final String getUserContractType = data.contractType;
final String getUserContractName = data.contractName;
if (getUserContractType == "지역") {
final userDataList = await ref
.watch(userRepo)
.getRegionUserData(getUserContractName);
final monthUserList =
await updateUserScore(userDataList, "종합 점수");
state = AsyncValue.data(monthUserList);
} else if (getUserContractType == "기관") {
final userDataList = await ref
.watch(userRepo)
.getCommunityUserData(getUserContractName);
final monthUserList =
await updateUserScore(userDataList, "종합 점수");
state = AsyncValue.data(monthUserList);
} else if (getUserContractType == "마스터" ||
getUserContractType == "전체") {
final userDataList = await ref.watch(userRepo).getAllUserData();
final monthUserList =
await updateUserScore(userDataList, "종합 점수");
state = AsyncValue.data(monthUserList);
}
},
error: (error, stackTrace) => print(error),
loading: () {},
);
state = const AsyncValue.data([]);
return [];
}
Future<List<UserModel?>> updateUserScore(
List<UserModel?> users, String sortOrder) async {
List<UserModel?> updateScoreList = [];
state = const AsyncValue.loading();
if (state.value!.isEmpty) {
await Future.forEach(
users,
(user) async {
final scoreUser = await calculateUserScore(user);
updateScoreList.add(scoreUser);
},
);
updateScoreList.sort((a, b) => b!.totalScore!.compareTo(a!.totalScore!));
final indexList = ref
.read(userProvider.notifier)
.indexUserModel(sortOrder, updateScoreList);
state = AsyncValue.data(indexList);
return indexList;
} else {
return state.value!;
}
}
Future<UserModel?> calculateUserScore(UserModel? user) async {
final String userId = user!.userId;
List<int> results;
List<Future<int>> futures = [
_rankingRepository.getUserStepScores(userId, firstDateOfMonth, now),
_rankingRepository.getUserDiaryScores(userId, firstDateOfMonth, now),
_rankingRepository.getUserCommentScores(userId, firstDateOfMonth, now),
];
results = await Future.wait(futures);
int userTotalScore = results[0] + results[1] + results[2];
final updatedUser = user.copyWith(
totalScore: userTotalScore,
stepScore: results[0],
diaryScore: results[1],
commentScore: results[2],
);
return updatedUser;
}
}
final monthRankingProvider =
AsyncNotifierProvider<MonthRankingViewModel, List<UserModel?>>(
() => MonthRankingViewModel(),
);
And from screen A's initState()
I call week and month viewmodels.
@override
void initState() {
super.initState();
ref.read(weekRankingProvider);
ref.read(monthRankingProvider);
}
I works well with week
data.
But error comes, when I tap 'month' sub-menu.
I guess, since build
method of month viewModel takes long to finished, when I go menu B and tap 'month' it is not still finished.
So according to my setting, when tapping month, because I set If I tap week, It draws week data, and if tapping month, draws month data.
void updateOrderPeriod(String value) async {
setState(() {
_sortOrderPeriod = value;
loadingFinished = false;
});
switch (value) {
case "이번달":
List<UserModel?> monthList = await ref
.read(monthRankingProvider.notifier)
.updateUserScore(_userDataList, _sortOrderStandard);
setState(() {
_userDataList = monthList;
loadingFinished = true;
});
break;
case "이번주":
setState(() {
_sortOrderPeriod = "이번주";
});
List<UserModel?> weekList = await ref
.read(weekRankingProvider.notifier)
.updateUserScore(_userDataList, _sortOrderStandard);
setState(() {
_userDataList = weekList;
loadingFinished = true;
});
break;
}
}
So even though build method is not finished,
await ref.read(monthRankingProvider.notifier).updateUserScore(_userDataList, _sortOrderStandard)
updateUserScore of this viewModel is ran again.
And It throws error since they crush.
I'm quite a newbie in Flutter, Please see my code and help me.
英文:
I have 3 menus in my Flutter
web: A, B, C.
And in menu B, it has sub menu 'week' and 'month'.
When I click menu 'month', It gets several data from Firestore
and compile it and draw UI.
But the problem is that Firestore is very slow to get several data.
(Ofcouse, month data is bigger than week data, so It takes much more time)
So If It start getting data when I click sub-menu 'month', It's too long to wait for loading finished.
My Idea is to start getting data when I access menu A, which means my loading start point moves forward in order to shorten loading time from user's point.
ViewModel
to extract month data is below:
class MonthRankingViewModel extends AsyncNotifier<List<UserModel?>> {
late RankingRepository _rankingRepository;
DateTime now = DateTime.now();
late DateTime firstDateOfMonth;
late List<UserModel?> monthUserList;
@override
FutureOr<List<UserModel?>> build() async {
_rankingRepository = RankingRepository();
DateTime now = DateTime.now();
firstDateOfMonth = DateTime(now.year, now.month, 1);
firstDateOfMonth = DateTime(firstDateOfMonth.year, firstDateOfMonth.month,
firstDateOfMonth.day, 0, 0);
ref.watch(contractConfigProvider).when(
data: (data) async {
final String getUserContractType = data.contractType;
final String getUserContractName = data.contractName;
if (getUserContractType == "지역") {
final userDataList = await ref
.watch(userRepo)
.getRegionUserData(getUserContractName);
final monthUserList =
await updateUserScore(userDataList, "종합 점수");
state = AsyncValue.data(monthUserList);
} else if (getUserContractType == "기관") {
final userDataList = await ref
.watch(userRepo)
.getCommunityUserData(getUserContractName);
final monthUserList =
await updateUserScore(userDataList, "종합 점수");
state = AsyncValue.data(monthUserList);
} else if (getUserContractType == "마스터" ||
getUserContractType == "전체") {
final userDataList = await ref.watch(userRepo).getAllUserData();
final monthUserList =
await updateUserScore(userDataList, "종합 점수");
state = AsyncValue.data(monthUserList);
}
},
error: (error, stackTrace) => print(error),
loading: () {},
);
state = const AsyncValue.data([]);
return [];
}
Future<List<UserModel?>> updateUserScore(
List<UserModel?> users, String sortOrder) async {
List<UserModel?> updateScoreList = [];
state = const AsyncValue.loading();
if (state.value!.isEmpty) {
await Future.forEach(
users,
(user) async {
final scoreUser = await calculateUserScore(user);
updateScoreList.add(scoreUser);
},
);
updateScoreList.sort((a, b) => b!.totalScore!.compareTo(a!.totalScore!));
final indexList = ref
.read(userProvider.notifier)
.indexUserModel(sortOrder, updateScoreList);
state = AsyncValue.data(indexList);
return indexList;
} else {
return state.value!;
}
}
Future<UserModel?> calculateUserScore(UserModel? user) async {
final String userId = user!.userId;
List<int> results;
List<Future<int>> futures = [
_rankingRepository.getUserStepScores(userId, firstDateOfMonth, now),
_rankingRepository.getUserDiaryScores(userId, firstDateOfMonth, now),
_rankingRepository.getUserCommentScores(userId, firstDateOfMonth, now),
];
results = await Future.wait(futures);
int userTotalScore = results[0] + results[1] + results[2];
final updatedUser = user.copyWith(
totalScore: userTotalScore,
stepScore: results[0],
diaryScore: results[1],
commentScore: results[2],
);
return updatedUser;
}
}
final monthRankingProvider =
AsyncNotifierProvider<MonthRankingViewModel, List<UserModel?>>(
() => MonthRankingViewModel(),
);
And from screen A's initState()
I call week and month viewmodels.
@override
void initState() {
super.initState();
ref.read(weekRankingProvider);
ref.read(monthRankingProvider);
}
I works well with week
data.
But error comes, when I tap 'month' sub-menu.
I guess, since build
method of month viewModel takes long to finished, when I go menu B and tap 'month' it is not still finished.
So according to my setting, when tapping month, because I set If I tap week, It draws week data, and if tapping month, draws month data.
void updateOrderPeriod(String value) async {
setState(() {
_sortOrderPeriod = value;
loadingFinished = false;
});
switch (value) {
case "이번달":
List<UserModel?> monthList = await ref
.read(monthRankingProvider.notifier)
.updateUserScore(_userDataList, _sortOrderStandard);
setState(() {
_userDataList = monthList;
loadingFinished = true;
});
break;
case "이번주":
setState(() {
_sortOrderPeriod = "이번주";
});
List<UserModel?> weekList = await ref
.read(weekRankingProvider.notifier)
.updateUserScore(_userDataList, _sortOrderStandard);
setState(() {
_userDataList = weekList;
loadingFinished = true;
});
break;
}
}
So even though build method is not finished,
await ref.read(monthRankingProvider.notifier).updateUserScore(_userDataList, _sortOrderStandard)
updateUserScore of this viewModel is ran again.
And It throws error since they crush.
I'm quite a newbie in Flutter, Please see my code and help me.
答案1
得分: 0
我看到了问题。问题在于,尽管构建方法尚未完成,但您正在调用MonthRankingViewModel上的updateUserScore方法。这是因为updateUserScore方法是异步的,所以它不会在从Firestore获取数据之前返回。
为了解决这个问题,您需要将updateUserScore方法包装在FutureBuilder中。这将确保构建方法在数据被获取之前不会完成。
以下代码显示了如何执行此操作:
void updateOrderPeriod(String value) async {
setState(() {
_sortOrderPeriod = value;
loadingFinished = false;
});
switch (value) {
case "이번달":
final monthList = await FutureBuilder(
future: ref
.read(monthRankingProvider.notifier)
.updateUserScore(_userDataList, _sortOrderStandard),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading...");
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else {
_userDataList = snapshot.data;
loadingFinished = true;
return Text("");
}
});
setState(() {
_userDataList = monthList;
loadingFinished = true;
});
break;
case "이번주":
setState(() {
_sortOrderPeriod = "이번주";
});
List<UserModel?> weekList = await ref
.read(weekRankingProvider.notifier)
.updateUserScore(_userDataList, _sortOrderStandard);
setState(() {
_userDataList = weekList;
loadingFinished = true;
});
break;
}
}
这段代码首先将updateUserScore方法包装在FutureBuilder中。这将创建一个Future,直到从Firestore获取数据之前都不会完成。
然后,FutureBuilder将用于构建UI。如果Future尚未完成,FutureBuilder将返回加载指示器。一旦Future完成,FutureBuilder将返回用户列表。
希望这有所帮助!如果您有任何其他问题,请随时告诉我。
英文:
I see the problem. The issue is that you are calling updateUserScore on the MonthRankingViewModel even though the build method has not finished. This is because the updateUserScore method is asynchronous, so it will not return until the data has been fetched from Firestore.
To fix this, you need to wrap the updateUserScore method in a FutureBuilder. This will ensure that the build method does not complete until the data has been fetched.
The following code shows how to do this:
void updateOrderPeriod(String value) async {
setState(() {
_sortOrderPeriod = value;
loadingFinished = false;
});
switch (value) {
case "이번달":
final monthList = await FutureBuilder(
future: ref
.read(monthRankingProvider.notifier)
.updateUserScore(_userDataList, _sortOrderStandard),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading...");
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else {
_userDataList = snapshot.data;
loadingFinished = true;
return Text("");
}
});
setState(() {
_userDataList = monthList;
loadingFinished = true;
});
break;
case "이번주":
setState(() {
_sortOrderPeriod = "이번주";
});
List<UserModel?> weekList = await ref
.read(weekRankingProvider.notifier)
.updateUserScore(_userDataList, _sortOrderStandard);
setState(() {
_userDataList = weekList;
loadingFinished = true;
});
break;
}
}
This code will first wrap the updateUserScore method in a FutureBuilder. This will create a Future that will not complete until the data has been fetched from Firestore.
The FutureBuilder will then be used to build the UI. If the Future is not yet complete, the FutureBuilder will return a loading indicator. Once the Future is complete, the FutureBuilder will return the list of users.
I hope this helps! Let me know if you have any other questions.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论