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

Flutter FutureBuilder重复调用Future两次问题如何解决

问题根因

所有传给FutureBuilder的Future直接在build方法内创建,属于官方明确禁止的错误用法。Flutter框架触发build的场景非常多:首次渲染、键盘弹出收起、系统主题/字体变化、父组件重绘、setState刷新,只要build执行,就会生成新的Future实例,FutureBuilder检测到新Future就会重新发起请求,你观察到的两次调用就是首次渲染流程中build触发两次导致的。
之前参考的方案未生效,核心原因是没有将Future实例缓存为State成员变量,仍然在build流程中临时创建Future。

修复步骤
  • 所有传给FutureBuilder的Future,统一在initState生命周期中初始化,存储为State的成员变量,禁止在build方法内直接调用方法生成Future传给FutureBuilder。
  • 如果Future需要依赖context或者widget传入的动态参数,可以在didChangeDependencies中初始化,保证同一个Future实例只创建一次。
  • 页面内嵌套的自定义子组件(你的GroupchatWidget)中的Future也要做相同处理,不能直接写在build方法里。
  • 如果后续需要依赖外部参数变化刷新Future(比如切换登录用户、切换群聊ID),在didUpdateWidget生命周期中判断参数是否变更,再给成员变量赋新的Future实例即可,不要在build里做判断更新。

修正后的代码示例

首先是首页代码:

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  LoggedUser loggedUser = Cache.getLoggedUser();
  // 提前声明需要用到的Future作为成员变量
  late final Future<ImageProvider> _userImageFuture;
  late final Future<List<Groupchat>> _groupchatListFuture;

  @override
  void initState() {
    super.initState();
    // initState仅在组件初始化时执行一次,不会重复调用
    _userImageFuture = Buttons.getUserImage(loggedUser.Id, context);
    _groupchatListFuture = Buttons.getGroupchatList(context);
  }

  static Widget showGroupchats(List<Groupchat> list) {
    List<Widget> children = [];
    for (int i = 0; i < list.length; i++) {
      children.add(GroupchatWidget(list[i]));
    }
    return Column(
      children: children,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Theme.of(context).backgroundColor,
      body: SingleChildScrollView(
        child: SafeArea(
          child: Column(
            children: <Widget>[
              Row(
                children: <Widget>[
                  Padding(
                    padding: const EdgeInsets.only(top: 15, left: 15),
                    child: FutureBuilder<ImageProvider>(
                      // 直接传入提前缓存好的Future实例
                      future: _userImageFuture,
                      builder: (BuildContext context,
                          AsyncSnapshot<ImageProvider> snapshot) {
                        return CircleAvatar(
                          radius: 20,
                          foregroundImage: snapshot.data,
                          backgroundImage: Settings.DefaultUserImage,
                        );
                      },
                    ),
                  ),
                  Padding(
                    padding: const EdgeInsets.only(left: 5, top: 10),
                    child: Text(
                      loggedUser.Username,
                      style: const TextStyle(fontSize: 20),
                    ),
                  ),
                  const Spacer(),
                  Padding(
                    padding: const EdgeInsets.only(top: 15, right: 15),
                    child: IconButton(
                        icon: SvgPicture.asset(
                          'assets/icons/settings.svg',
                          width: 25,
                          height: 25,
                          color: Theme.of(context).textTheme.bodyText1?.color,
                        ),
                        onPressed: null
                        ),
                  ),
                ],
              ),
              Row(
                children: <Widget>[
                  Flexible(
                    flex: 1,
                    child: Padding(
                      padding:
                          const EdgeInsets.only(left: 15, right: 15, top: 15),
                      child: TextField(
                        decoration: InputDecoration(
                          border: const OutlineInputBorder(),
                          hintText: 'groupchatSearch'.tr(),
                          prefixIcon: Container(
                            width: 5,
                            height: 5,
                            padding: const EdgeInsets.all(10),
                            child: SvgPicture.asset('assets/icons/search.svg',
                                color: Theme.of(context)
                                    .textTheme
                                    .bodyText1
                                    ?.color),
                          ),
                        ),
                      ),
                    ),
                  ),
                ],
              ),
              Padding(
                padding: const EdgeInsets.only(left: 15, right: 15, top: 5),
                child: FutureBuilder<List<Groupchat>>(
                  // 传入缓存好的Future实例
                  future: _groupchatListFuture,
                  builder: (BuildContext context,
                      AsyncSnapshot<List<Groupchat>> snapshot) {
                    if (snapshot.hasData == false) {
                      return Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: const <Widget>[
                          Padding(
                            padding: EdgeInsets.only(top: 15),
                            child: CircularProgressIndicator(),
                          ),
                        ],
                      );
                    } else {
                      return showGroupchats(snapshot.data!);
                    }
                  },
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

然后是群聊组件代码:

class GroupchatWidget extends StatefulWidget {
  Groupchat groupchat;

  @override
  State<GroupchatWidget> createState() => _GroupchatWidgetState();

  GroupchatWidget(this.groupchat, {Key? key}) : super(key: key);
}

class _GroupchatWidgetState extends State<GroupchatWidget> {
  // 提前声明子组件用到的Future作为成员变量
  late final Future<ImageProvider> _groupchatImageFuture;
  late final Future<void> _newMessageFuture;

  @override
  void initState() {
    super.initState();
    // 初始化时仅执行一次
    _groupchatImageFuture = Buttons.getGroupchatImage(widget.groupchat.Id, context);
    _newMessageFuture = Buttons.getNewGroupchatMessages(widget.groupchat.Id, context);
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(top: 5, bottom: 5),
      child: Container(
        decoration: BoxDecoration(
            color: Theme.of(context).focusColor,
            borderRadius: BorderRadius.circular(15)),
        child: ListTile(
          leading: FutureBuilder<ImageProvider>(
            // 传入缓存的Future实例
            future: _groupchatImageFuture,
            builder:
                (BuildContext context, AsyncSnapshot<ImageProvider> snapshot) {
              return CircleAvatar(
                radius: 25,
                foregroundImage: snapshot.data,
                backgroundImage: Settings.DefaultGroupchatImage,
              );
            },
          ),
          title: Text(
            widget.groupchat.Name,
            style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
            overflow: TextOverflow.ellipsis,
          ),
          subtitle: FutureBuilder<void>(
            // 传入缓存的Future实例
            future: _newMessageFuture,
            builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
              return Text(
                Cache.getLastMessage(widget.groupchat.Id).Text,
                style: const TextStyle(fontSize: 15),
                overflow: TextOverflow.ellipsis,
              );
            },
          ),
        ),
      ),
    );
  }
}

FutureBuilder本身不会对传入的Future做任何缓存处理,只要接收到新的Future实例就会重新执行请求流程,这是组件的设计逻辑,不是bug。

运行效果截图

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 11:06:23