You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 07:45:48