用一个 Mixin 收拢列表协议
项目的 ListMixin
刷新先把页码重置为 1,清除“没有更多”状态,然后用接口结果替换列表。加载更多则先增加页码,把结果追加到列表;请求失败时将页码回退。这个顺序让重试仍会请求正确的下一页。
网络响应可能晚于页面关闭
页面退出时,列表控制器可能正在等待 API。当前实现加入 isDisposed 标记:销毁后不再更新列表或调用刷新控制器;正在飞行的请求返回时也先检查标记。
Future<void> fetchListData({bool isRefresh = false}) async {
final result = await fetchApi();
if (isDisposed) {
return;
}
if (isRefresh) {
list = result;
} else {
list.addAll(result);
}
updateView();
}
disposeListResources() 先设置标记,再通过一个 microtask 延迟释放 RefreshController。这在特定的 SmartRefresher 卸载顺序下,能避免父子 Widget 拆除时控制器状态提前失效。
这套保护需要每个调用方正确接入
Mixin 只能保护使用它且在 onClose() 调用 disposeListResources() 的控制器。若某个列表页直接同步 dispose 控制器,或刷新流程没有 disposed 检查,仍可能在异步返回后触碰已释放对象。
同时,标记只处理销毁,不会自动解决“同时下拉刷新和加载更多”的请求竞态。若产品允许快速触发多个请求,应考虑加载锁、请求取消或序号;还要决定刷新期间是否允许继续追加旧页结果。
边界测试建议
- 刷新成功时替换旧数据,短页结果会关闭“加载更多”。
- 加载更多成功时只追加一次。
- 加载失败时恢复页码,之后可重试。
- 控制器销毁后,延迟返回的结果不调用 UI 更新或刷新控制器。
- 快速连续刷新不会让旧响应覆盖新结果。
- 空列表和第一页就没有数据时,SmartRefresher 显示正确状态。
经验总结
ListMixin 这套收拢方式是对的:页码、hasMore、加载状态集中管理,刷新重置页码、加载更多失败回退页码,顺序都经得起推敲。isDisposed 标记防”页面关了请求才回来”也是列表页的标准做法。但示例应用有个缺口:标记只防销毁,不防”同时下拉刷新和加载更多”的竞态——快速连点可能让旧页结果覆盖新数据。如果是我来写,会加一个请求序号(和定位篇的 requestVersion 同一个思路),响应回来先对序号。SmartRefresher 就是 pull_to_refresh 包,API 对得上,onClose 里别忘了 refreshController.dispose()。
小结
列表组件除了分页接口,还要管理 Widget、控制器和异步请求的生命周期。ListMixin 将通用规则集中起来,而调用方是否接入销毁保护、并发请求是否需要额外控制,仍要逐页确认。
