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
相关产品推荐
相关产品推荐

