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

Flutter交互回调中使用FutureBuilder时future方法被两次调用如何解决

问题原因

  • 你在LoadMoreWidget的build方法中重复调用了widget.loadMoreViewBuilder(context):一次是在水平/垂直拖动结束的回调中,一次是在Stack的子组件渲染时。每次调用该方法都会构造新的FutureBuilder,而你给future属性传的是updateData()(直接执行方法返回Future),所以每调用一次就会触发一次updateData执行,自然会调用两次。
  • 你没有对FutureBuilder的future做缓存,只要组件重绘就会生成新的Future实例,也会导致updateData重复执行。
  • 核心逻辑设计错误:不应该通过强制转换Widget拿到Future执行加载逻辑,加载逻辑应该和UI组件解耦。

修复方案

步骤1:给主页状态加加载锁和Future缓存

修改_MyHomePageState代码,新增加载状态判断和Future缓存变量,避免重复触发加载:

class _MyHomePageState extends State<MyHomePage> {
  late List<SampleData> chartData;
  bool _isLoading = false; // 加载状态锁,避免重复触发
  Future<List<SampleData>>? _updateFuture; // 缓存加载用的Future实例

  @override
  void initState() {
    getChartData();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Center(
          child: LoadMoreWidget(
              data: chartData,
              loadMoreViewBuilder: onLoadMoreWidgetCalled,
              onLoadMore: triggerLoadMore // 传入加载触发方法
          ),
        ));
  }

  Widget onLoadMoreWidgetCalled(BuildContext context) {
    return FutureBuilder(
        future: _updateFuture, // 绑定缓存的Future,不直接执行方法
        builder: (context, snapshot) {
          if ((snapshot.hasData && !snapshot.hasError) || !_isLoading) {
            return const SizedBox.shrink();
          } else {
            return getProgressIndicator();
          }
        });
  }

  // 抽离公共加载触发方法,统一控制加载状态
  Future<void> triggerLoadMore() async {
    if (_isLoading) return;
    setState(() {
      _isLoading = true;
      _updateFuture = updateData();
    });
    await _updateFuture;
    setState(() {
      _isLoading = false;
    });
  }

  // 其余原有方法(getChartData、getProgressIndicator、updateData)保持不变
}

步骤2:修改自定义Widget的逻辑,解耦加载逻辑和UI

给LoadMoreWidget新增加载触发回调参数,不要在拖动回调中强行操作Widget实例拿Future:

class LoadMoreWidget extends StatefulWidget {
  const LoadMoreWidget({
    Key? key,
    required this.data,
    required this.loadMoreViewBuilder,
    required this.onLoadMore, // 新增加载触发回调参数
  }) : super(key: key);

  final List<dynamic> data;
  final LoadMoreViewBuilderCallback loadMoreViewBuilder;
  final Future<void> Function() onLoadMore; // 回调类型定义

  @override
  State<LoadMoreWidget> createState() => _LoadMoreWidgetState();
}

class _LoadMoreWidgetState extends State<LoadMoreWidget> {
  @override
  Widget build(BuildContext context) {
    List<dynamic> data = widget.data;
    return LayoutBuilder(
        builder: (BuildContext context, BoxConstraints constraints) {
      return GestureDetector(
          onHorizontalDragEnd: (DragEndDetails details) async {
            await widget.onLoadMore(); // 直接调用加载回调
            setState(() {});
          },
          onVerticalDragEnd: (DragEndDetails details) async {
            await widget.onLoadMore(); // 直接调用加载回调
            setState(() {});
          },
          child: Stack(children: [
            ListView.builder(
              itemBuilder: (context, index) {
                return Card(
                    child: Row(
                  children: [
                    Text('x:${data[index].x}'),
                    Text('y:${data[index].y}'),
                  ],
                ));
              },
              itemCount: widget.data.length,
            ),
            // 仅在这里调用一次构造加载UI,不再重复调用
            widget.loadMoreViewBuilder(context)
          ]));
    });
  }
}

修改完成后,updateData方法仅会在拖动结束时触发一次,不会重复执行。

内容的提问来源于stack exchange,提问作者Kanagambigai Murugan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 10:18:01