列表刷新是最常见的业务场景,问题也最多:重复请求、分页乱序、空态难处理。下面以 easy_refresh 为例,给出一套“能直接用”的完整方案。
0. 依赖
dependencies:
easy_refresh: ^3.4.0
1. 定义分页状态模型
class PageState<T> {
final List<T> items;
final bool loading;
final bool hasMore;
final String? error;
const PageState({
this.items = const [],
this.loading = false,
this.hasMore = true,
this.error,
});
PageState<T> copyWith({
List<T>? items,
bool? loading,
bool? hasMore,
String? error,
}) {
return PageState<T>(
items: items ?? this.items,
loading: loading ?? this.loading,
hasMore: hasMore ?? this.hasMore,
error: error,
);
}
}
2. 控制器与数据加载
class ListController<T> extends ChangeNotifier {
final Future<List<T>> Function(int page) loader;
final EasyRefreshController refreshController = EasyRefreshController();
PageState<T> state = const PageState();
int _page = 1;
ListController({required this.loader});
Future<void> refresh() async {
_page = 1;
state = state.copyWith(loading: true, error: null);
notifyListeners();
try {
final data = await loader(_page);
state = state.copyWith(items: data, hasMore: data.isNotEmpty, loading: false);
refreshController.finishRefresh();
} catch (e) {
state = state.copyWith(loading: false, error: e.toString());
refreshController.finishRefresh(IndicatorResult.fail);
}
notifyListeners();
}
Future<void> loadMore() async {
if (!state.hasMore) return;
try {
final data = await loader(_page + 1);
_page += 1;
state = state.copyWith(items: [...state.items, ...data], hasMore: data.isNotEmpty);
refreshController.finishLoad(data.isEmpty ? IndicatorResult.noMore : IndicatorResult.success);
} catch (e) {
refreshController.finishLoad(IndicatorResult.fail);
}
notifyListeners();
}
}
3. UI 使用方式
ChangeNotifierProvider(
create: (_) => ListController(loader: fetchPage)..refresh(),
child: Consumer<ListController>(builder: (context, ctrl, _) {
if (ctrl.state.error != null) return ErrorView(onRetry: ctrl.refresh);
if (ctrl.state.items.isEmpty) return const EmptyView();
return EasyRefresh(
controller: ctrl.refreshController,
onRefresh: ctrl.refresh,
onLoad: ctrl.loadMore,
child: ListView.builder(
itemCount: ctrl.state.items.length,
itemBuilder: (_, i) => Text(ctrl.state.items[i].toString()),
),
);
}),
)
4. 只刷新 / 只加载
// 只刷新
EasyRefresh(onRefresh: ctrl.refresh, child: ListView(...))
// 只加载
EasyRefresh(onLoad: ctrl.loadMore, child: ListView(...))
5. 主动触发刷新
ctrl.refreshController.callRefresh();
6. 结束状态
refreshController.finishRefresh(IndicatorResult.success);
refreshController.finishLoad(IndicatorResult.noMore);
7. 常见坑点
- 重复触发:刷新与加载不要同时改同一套分页参数
- hasMore 失控:空列表必须立即
noMore - 错误态不清理:错误后要允许手动重试
8. 实践清单
- 分页状态集中管理
- 刷新/加载分离
- 空态/错误态明确
- 控制器统一收敛