Flutter通用状态管理插件generic_state_bya2的使用
Flutter通用状态管理插件generic_state_bya2的使用
generic_state_bya2
通用状态管理插件。此包消除了类型转换的麻烦,并具有处理API、分页响应或您自己的用例的方法和类。
注意事项
根据项目的不同需求和设置,GenericState总是有所不同的。因此,为了完全自定义此包,我建议克隆GitHub仓库并根据您的需求进行编辑。如果您这样做,您可以更好地自定义加载指示器、错误图形、无数据图形等。
使用方法
控制器
普通API请求
您可以用以下方式处理普通API请求:
Future<void> getValue({bool isRefresh = false}) async {
await Future.delayed(Duration.zero);
if (!isRefresh) {
state = LoadingState();
}
try {
final repositoryData = await repository.getData();
state = SuccessState(repositoryData);
// 或者 state = SuccessState.pagination(...) 如果是分页类型
} catch (e, s) {
if (state.showToastInError(isRefresh)) {
showCustomToast(e.toString());
} else {
state = ErrorState(e, s);
}
}
}
分页API请求
您可以用以下方式处理分页API请求:
Future<void> fetchValue({
bool isRefresh = false,
bool isPagination = false,
}) async {
if (state.isPaginationLoading) return;
await Future.delayed(Duration.zero);
if (state.showLoading(isRefresh, isPagination)) {
state = LoadingState();
}
try {
if (isPagination) {
state = state.copyToTogglePaginationLoading(true);
}
final repositoryData = await repository.getData(
pageIndex: state.nextPage(isRefresh),
);
state = state.copyOfNextOrRefresh(
response: repositoryData,
isRefresh: isRefresh,
oldPlusNewData: () => [
...?state.dataOrNull,
...repositoryData.data,
],
);
} catch (e, s) {
if (state.showToastInError(isRefresh)) {
showCustomToast(e.toString());
} else {
state = ErrorState(e, s);
}
}
}
对于分页请求,您需要将存储库数据的返回类型设置为PaginationResponse
。
辅助方法
此通用状态最好的部分在于它提供了许多辅助方法,这些方法可以消除类型转换的麻烦。
在UI中,您需要获取值,有时如果状态不是成功状态则需要提供替代值,有时需要映射值。
final state = SuccessState(1);
final data = state.dataOrNull;
final data2 = state.dataOr(5);
final data3 = state.dataInKOr<String>(
onData: (data) => data.toString(),
alternative: "NULL",
);
final data4 = state.when<int>(
success: (state) => state.data * 2,
error: (state) => state.error.hashCode,
loading: () => 0,
);
以上是该包提供的辅助方法。 您可以在场景中使用这些方法,例如在应用程序头部的用户名。当成功时,您可能需要设置用户名,但当状态正在加载或其他状态时,您想向用户显示“Loading…”文本。因此,您可以这样做:
Text(
state.dataOrNull?.userName ?? "Loading...",
style: TextStyle(
fontSize: 16,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
或者,您可以在成功、错误和加载时设置不同的值:
Text(
state.when(
success: (state) => state.data.fullName,
error: (state) => "-",
loading: () => "Loading...",
),
style: TextStyle(
fontSize: 16,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
最后,它还提供了状态检查方法,如:
state.isLoading;
state.isNotLoading;
state.isSuccess;
state.isNotSuccess;
state.isError;
state.isNotError;
这些方法可以在以下情况下使用:
#1
IgnorePointer(
ignoring: state.isLoading,
...
)
#2
Future<void> mayFetchOrUseCache() async {
if (state.isNotSuccess) {
await fetchTheData();
}
}
#3
if (state.isLoading) {
return LinearProgressIndicator(
...
);
} else {
return SizedBox(
...
);
}
GenericStateWidget 和 GenericStatePaginationWidget
GenericStateWidget
和 GenericStatePaginationWidget
在处理应用中的API响应时非常有用。此包处理了开发人员在集成API响应时需要考虑的所有复杂情况。
GenericStateWidget(
state: ref.watch(homeProvider),
onSuccess: (state) => Column(
...
),
onErrorReload: () async {
ref.read(homeProvider.notifier).loadData();
},
isEmptyCheck: (state) => state.data.isEmpty,
onRefresh: () async {
await ref.read(homeProvider.notifier).loadData(isRefresh: true);
},
loadingShimmer: () => const HomeLoadingShimmer(),
),
最有趣的部分是分页功能。通过此包,您无需担心分页的复杂性。此包处理了滚动控制器的监听, 在需要分页时调用合适的方法,并显示分页加载。
final ScrollController scrollController = ScrollController();
@override
Widget build(BuildContext context) {
final state = ref.watch(friendListProvider(widget.type));
return GenericStatePaginationWidget(
state: state,
scrollController: scrollController,
onRefresh: () async {
await ref
.read(friendListProvider(widget.type).notifier)
.getFriends(ref, isRefresh: true);
},
isEmptyCheck: (state) => state.data.isEmpty,
onSuccess: (state) {
return ListView.builder(
controller: scrollController,
itemCount: state.data.length,
itemBuilder: (context, index) {
return IndividualFriendWidget(
item: state.data[index],
);
},
);
},
toFetchNextPage: () {
ref
.read(friendListProvider(widget.type).notifier)
.getFriends(ref, isPagination: true);
},
onErrorReload: () async {
ref
.read(friendListProvider(widget.type).notifier)
.getFriends(ref);
},
loadingShimmer: () => const FriendsLoadingShimmer(),
);
}
@override
void dispose() {
scrollController.dispose();
super.dispose();
}
请注意以下几点:
- 确保您已将
ScrollController
传递到可滚动的UI部分。 - 释放
ScrollController
。重复一遍,释放ScrollController
。不这样做不会导致包或应用程序出现问题,但它会导致内存泄漏,而一个好的程序员总是会处理这种情况。
有时GenericStateWidget
或 GenericStatePaginationWidget
在CustomScrollView
中,这意味着父级期望子元素是一个Sliver
而不是普通的RenderBox
。
在这种情况下,您可以传递isSliver: true
,默认情况下它是false
。
GenericStatePaginationWidget(
isSliver: true,
...
),
请注意以下几点:
- 如果
isSliver
为true
,您传递的onRefresh
代码永远不会运行。因为您需要在父类中处理onRefresh
,即CustomScrollView
。 - 类似地,与普通的分页小部件不同,当
isSliver
为true
时,底部加载指示器在加载下一页时将无法工作,您需要手动在ListView
的最后一项上设置加载。
GenericStatePaginationWidget(
onSuccess: (state) {
return SliverList(
delegate: SliverChildBuilderDelegate(
(_, int index) {
final bool showPaginationLoading = state.isPaginationLoading && index == state.data.length - 1;
return IndividualFriendWidget(
item: state.data[index],
showPaginationLoading: showPaginationLoading,
);
},
childCount: state.data.length,
),
);
},
...
),
// IndividualFriendWidget
Column(
children: [
...
if (showPaginationLoading)
const Center(child: CircularProgressIndicator()),
],
)
PaginationResponse
多亏了PaginationResponse
,我们的包可以确定页面是否有下一页,从而相应地加载下一页或不加载。并且在分页方面帮助了许多其他设置。
// 设置PaginationResponse,通常在主函数中,如果不这样做,将使用默认值。
PaginationResponseSetup.setup(
haveNext: (response, pageNumber) {
return response["totalPages"] > pageNumber;
},
paramsMap: (pageIndex, pageSize) {
return {
"pageIndex": pageIndex,
"pageSize": pageSize,
};
},
pageSize: 20,
);
// 在您的存储库中返回PaginationResponse。
class FriendsRepository extends BaseRepository {
Future<PaginationResponse<List<Friend>>> getFriendList({required int pageIndex}) async {
return await get<PaginationResponse<List<Friend>>>(
RequestInput(
url: ApiConstants.getAllFriends,
params: PaginationResponse.params(pageIndex), // PaginationResponse类提供的参数
body: null,
parseJson: (response) {
return PaginationResponse( // 在API获取和JSON解析后返回PaginationResponse
data: (response["data"] as List)
.map((e) => Friend.fromJson(e))
.toList(),
response: response,
pageIndex: pageIndex,
);
},
),
);
}
// 并像这样使用它
final response = await FriendsRepository().getFriendList(pageIndex: 1);
responseData.data;
responseData.haveNext;
responseData.pageIndex;
responseData.oldPlusNew([
...?state.dataOrNull, // 旧数据
...responseData.data, // 新数据
]);
// 并在控制器中发出状态以进行分页
emit(SuccessState.pagination(...)); // BLOC
state = SuccessState.pagination(...); // Riverpod
}
更多关于Flutter通用状态管理插件generic_state_bya2的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html