Flutter使用pull_to_refresh分页时遇Concurrent modification异常求助
解决Flutter pull_to_refresh分页追加数据的并发修改错误
错误原因分析
你遇到的Concurrent modification during iteration错误,是因为直接对snapshot.data!.data!执行addAll(snapshot.data!.data!)——这相当于把列表自身的元素重复添加到自己,同时snapshot.data是FutureBuilder返回的不可变对象,直接修改会触发迭代时的并发修改异常。而且你的代码逻辑没有把新分页的数据真正追加到现有列表,而是错误地复用了当前页的数据。
正确的实现方案
核心思路是把列表数据存储在状态变量中,通过setState更新数据,而不是直接修改FutureBuilder的snapshot数据。
1. 添加状态变量
在你的State类中定义用于存储所有数据的变量:
int currentPage = 1; bool isRefresh = false; List<YourDataModel> _allPosts = []; // 替换成UserPost.data对应的实际数据类型 bool _hasNextPage = true; Future<UserPost>? _fetchFuture; // 存储请求Future,避免重复请求
2. 初始化首次请求
在initState中发起第一次数据请求:
@override void initState() { super.initState(); _fetchFuture = getUserPost(); }
3. 修改请求方法,处理数据追加/替换
更新getUserPost方法,根据刷新/加载状态处理数据:
Future<UserPost> getUserPost() async { var url = "https://myApiLink/getUserPost.php?page=$currentPage"; var response = await http.get(Uri.parse(url)); var jsondata = jsonDecode(response.body.toString()); var apiData = UserPost.fromJson(jsondata); if (response.statusCode == 200) { setState(() { if (isRefresh) { // 下拉刷新:替换为新数据 _allPosts = apiData.data!; isRefresh = false; } else { // 上拉加载:追加新数据 _allPosts.addAll(apiData.data!); } // 根据接口返回更新是否有下一页 _hasNextPage = apiData.hasNextPage == 1; }); // 更新刷新控制器状态 if (isRefresh) { refreshController.refreshCompleted(); } else { _hasNextPage ? refreshController.loadComplete() : refreshController.loadNoData(); } } else { // 请求失败时更新控制器状态 if (isRefresh) { refreshController.refreshFailed(); } else { refreshController.loadFailed(); } } return apiData; }
4. 更新FutureBuilder和刷新逻辑
修改FutureBuilder使用状态变量作为数据源,同时调整刷新/加载回调:
FutureBuilder<UserPost>( future: _fetchFuture, builder: (context, snapshot) { return SmartRefresher( controller: refreshController, enablePullUp: _hasNextPage, onRefresh: () async { await Future.delayed(const Duration(milliseconds: 1000)); setState(() { isRefresh = true; currentPage = 1; _fetchFuture = getUserPost(); }); }, onLoading: () async { if (!_hasNextPage) { refreshController.loadNoData(); return; } await Future.delayed(const Duration(milliseconds: 1000)); setState(() { currentPage++; _fetchFuture = getUserPost(); }); }, child: ListView.builder( keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, itemCount: _allPosts.length, itemBuilder: (context, i) { var postList = _allPosts[i]; String tempSearch = postList.azakhanaName.toString(); // 这里写你的列表项UI代码 return ListTile(title: Text(tempSearch)); }, ), ); }, )
关键注意点
- 不要直接修改FutureBuilder的
snapshot.data,必须通过状态变量管理数据。 - 使用
_fetchFuture存储请求实例,避免每次Widget重建都重复发起网络请求。 - 严格区分下拉刷新(重置数据)和上拉加载(追加数据)的逻辑。
- 根据接口返回的
hasNextPage控制是否允许继续上拉加载,避免无效请求。
内容的提问来源于stack exchange,提问作者Taki Rajani
相关产品推荐
相关产品推荐

