名称: asyncredux-wait-fail-succeed
描述: 在组件中显示加载状态和处理操作失败。覆盖 isWaiting(ActionType) 用于旋转器,isFailed(ActionType) 用于错误状态,exceptionFor(ActionType) 用于错误消息,以及 clearExceptionFor() 用于重置失败状态。
异步Redux等待、失败、成功
AsyncRedux 提供了上下文扩展方法来跟踪异步操作状态:等待(进行中)、失败(错误)和成功(完成)。这些对于在UI中显示旋转器、错误消息和成功状态至关重要。
四个核心方法
| 方法 | 返回 | 目的 |
|---|---|---|
isWaiting(ActionType) |
bool |
如果操作当前正在运行,则为真 |
isFailed(ActionType) |
bool |
如果操作最近失败,则为真 |
exceptionFor(ActionType) |
UserException? |
来自失败操作的异常 |
clearExceptionFor(ActionType) |
void |
手动清除存储的异常 |
显示加载旋转器
使用 isWaiting() 在操作运行时显示旋转器:
Widget build(BuildContext context) {
if (context.isWaiting(FetchDataAction)) {
return CircularProgressIndicator();
}
return Text('数据: ${context.state.data}');
}
组件在操作开始和完成时自动重建。
显示错误状态
使用 isFailed() 和 exceptionFor() 显示错误消息:
Widget build(BuildContext context) {
if (context.isFailed(FetchDataAction)) {
var exception = context.exceptionFor(FetchDataAction);
return Text('错误: ${exception?.message}');
}
return Text('数据: ${context.state.data}');
}
组合模式:加载、错误和成功
典型模式处理所有三种状态:
Widget build(BuildContext context) {
// 加载状态
if (context.isWaiting(GetItemsAction)) {
return Center(child: CircularProgressIndicator());
}
// 错误状态带重试
if (context.isFailed(GetItemsAction)) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('加载项目失败'),
Text(context.exceptionFor(GetItemsAction)?.message ?? ''),
ElevatedButton(
onPressed: () => context.dispatch(GetItemsAction()),
child: Text('重试'),
),
],
);
}
// 成功状态
return ListView.builder(
itemCount: context.state.items.length,
itemBuilder: (context, index) => ListTile(
title: Text(context.state.items[index].name),
),
);
}
自动错误清除
当操作再次被调度时,该操作类型的任何先前错误都会自动清除。这意味着:
- 用户看到错误
- 用户点击“重试”再次调度操作
isFailed()立即变为假isWaiting()变为真- 如果操作成功,组件显示成功状态
- 如果操作再次失败,
isFailed()变为真,并带有新异常
手动错误清除
使用 clearExceptionFor() 当需要在不重试的情况下关闭错误时:
Widget build(BuildContext context) {
if (context.isFailed(SubmitFormAction)) {
return AlertDialog(
title: Text('错误'),
content: Text(context.exceptionFor(SubmitFormAction)?.message ?? ''),
actions: [
TextButton(
onPressed: () {
context.clearExceptionFor(SubmitFormAction);
},
child: Text('关闭'),
),
TextButton(
onPressed: () => context.dispatch(SubmitFormAction()),
child: Text('重试'),
),
],
);
}
// ...
}
操作如何失败
当操作在 before() 或 reduce() 中抛出错误时会失败。使用 UserException 处理面向用户的错误:
class FetchDataAction extends ReduxAction<AppState> {
@override
Future<AppState?> reduce() async {
final response = await api.fetchData();
if (response.statusCode == 404) {
throw UserException('数据未找到。');
}
if (response.statusCode != 200) {
throw UserException('加载数据失败。请重试。');
}
return state.copy(data: response.data);
}
}
检查多个操作
可以检查多个操作类型的等待或失败:
Widget build(BuildContext context) {
// 检查是否有任何操作正在运行
bool isLoading = context.isWaiting(FetchUserAction) ||
context.isWaiting(FetchSettingsAction);
if (isLoading) {
return CircularProgressIndicator();
}
// 检查是否有任何失败
if (context.isFailed(FetchUserAction)) {
return Text('加载用户失败');
}
if (context.isFailed(FetchSettingsAction)) {
return Text('加载设置失败');
}
return MyContent();
}
下拉刷新集成
与 dispatchAndWait() 结合用于刷新指示器:
class MyListWidget extends StatelessWidget {
Future<void> _onRefresh(BuildContext context) {
return context.dispatchAndWait(RefreshItemsAction());
}
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: () => _onRefresh(context),
child: ListView.builder(
itemCount: context.state.items.length,
itemBuilder: (context, index) => ListTile(
title: Text(context.state.items[index].name),
),
),
);
}
}
完整示例
class LoadProductsAction extends ReduxAction<AppState> {
@override
Future<AppState?> reduce() async {
final products = await api.fetchProducts();
if (products.isEmpty) {
throw UserException('没有可用的产品。');
}
return state.copy(products: products);
}
}
class ProductsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('产品')),
body: _buildBody(context),
floatingActionButton: FloatingActionButton(
onPressed: () => context.dispatch(LoadProductsAction()),
child: Icon(Icons.refresh),
),
);
}
Widget _buildBody(BuildContext context) {
if (context.isWaiting(LoadProductsAction)) {
return Center(child: CircularProgressIndicator());
}
if (context.isFailed(LoadProductsAction)) {
final error = context.exceptionFor(LoadProductsAction);
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error, size: 64, color: Colors.red),
SizedBox(height: 16),
Text(error?.message ?? '发生错误'),
SizedBox(height: 16),
ElevatedButton(
onPressed: () => context.dispatch(LoadProductsAction()),
child: Text('再试一次'),
),
],
),
);
}
final products = context.state.products;
if (products.isEmpty) {
return Center(child: Text('还没有产品。点击刷新加载。'));
}
return ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) => ListTile(
title: Text(products[index].name),
subtitle: Text('\$${products[index].price}'),
),
);
}
}
参考
文档URL:
- https://asyncredux.com/flutter/basics/wait-fail-succeed
- https://asyncredux.com/flutter/miscellaneous/advanced-waiting
- https://asyncredux.com/flutter/advanced-actions/action-status
- https://asyncredux.com/flutter/basics/failed-actions
- https://asyncredux.com/flutter/advanced-actions/errors-thrown-by-actions
- https://asyncredux.com/flutter/basics/using-the-store-state
- https://asyncredux.com/flutter/basics/dispatching-actions
- https://asyncredux.com/flutter/basics/async-actions
- https://asyncredux.com/flutter/miscellaneous/refresh-indicators