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

Flutter中如何让两个FutureBuilder等待同一个Future?

这个场景我太熟悉了!之前做项目时也碰到过两个页面重复请求同一份数据的问题,FutureProvider绝对是可行且非常合适的解决方案,另外还有一种基于你现有ChangeNotifier的方案也能搞定,我给你详细拆解:

方案一:使用FutureProvider(推荐)

这是最直接的方案,FutureProvider的核心作用就是在组件树中共享一个Future的状态,所有依赖它的子组件(不管是哪个页面)都会复用同一个Future实例,从根源上避免重复调用。

具体步骤:

  1. 准备依赖和基础代码
    先确保你在pubspec.yaml里添加了状态管理包,如果你用官方的provider:

    dependencies:
      provider: ^6.0.5
    

    要是用更推荐的新一代状态管理flutter_riverpod,就加这个:

    dependencies:
      flutter_riverpod: ^2.3.0
    

    然后定义你的数据模型和获取数据的Future:

    class Item {
      final String name;
      Item(this.name);
    }
    
    // 模拟网络请求的Future
    Future<List<Item>> fetchItems() async {
      await Future.delayed(const Duration(seconds: 2));
      return [Item("Item 1"), Item("Item 2"), Item("Item 3")];
    }
    
  2. 在共同父组件注入FutureProvider
    把FutureProvider放在两个页面的共同父节点(比如MaterialApp下面或者路由导航器的上层),这样两个页面都能获取到这个共享的Future:

    // 以provider包为例的写法
    void main() {
      runApp(
        // 保留你原来的ChangeNotifierProvider(如果需要继续用的话)
        ChangeNotifierProvider(
          create: (context) => MyNotifier(),
          child: FutureProvider<List<Item>>(
            create: (context) => fetchItems(),
            initialData: const [], // 可选:设置初始空数据
            child: const MaterialApp(
              home: HomePage(), // 这里是包含两个目标页面的入口
            ),
          ),
        ),
      );
    }
    
  3. 在页面中复用共享Future
    两个页面里的FutureBuilder不用自己创建Future,直接从FutureProvider中获取共享实例就行:

    class PageA extends StatelessWidget {
      const PageA({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text("页面A")),
          body: FutureBuilder<List<Item>>(
            // 从Provider获取共享的Future,不会重复调用fetchItems()
            future: Provider.of<Future<List<Item>>>(context),
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {
                return const Center(child: CircularProgressIndicator());
              } else if (snapshot.hasError) {
                return Center(child: Text("出错了:${snapshot.error}"));
              } else {
                // 如果你需要把数据同步到ChangeNotifier里,在这里调用即可
                // Provider.of<MyNotifier>(context, listen: false).updateItems(snapshot.data!);
                return ListView.builder(
                  itemCount: snapshot.data!.length,
                  itemBuilder: (context, index) => ListTile(title: Text(snapshot.data![index].name)),
                );
              }
            },
          ),
        );
      }
    }
    

    页面B的写法完全一样,这样两个页面的FutureBuilder都会监听同一个Future,只会触发一次数据请求。

方案二:基于现有ChangeNotifier改造

如果你更习惯用原来的ChangeNotifierProvider,也可以把Future的执行逻辑封装到ChangeNotifier里,确保只执行一次:

具体步骤:

  1. 修改你的ChangeNotifier
    给它加上加载状态判断,避免重复调用Future:

    class MyNotifier extends ChangeNotifier {
      List<Item> _items = [];
      bool _isLoading = false;
      String? _errorMsg;
    
      List<Item> get items => _items;
      bool get isLoading => _isLoading;
      String? get errorMsg => _errorMsg;
    
      // 只执行一次的加载方法
      Future<void> loadItems() async {
        // 如果已经加载过或者正在加载,直接返回
        if (_items.isNotEmpty || _isLoading) return;
        
        _isLoading = true;
        notifyListeners();
        
        try {
          final fetchedItems = await fetchItems();
          _items = fetchedItems;
        } catch (e) {
          _errorMsg = e.toString();
        } finally {
          _isLoading = false;
          notifyListeners();
        }
      }
    }
    
  2. 在页面中监听状态
    不用FutureBuilder,直接用Consumer监听ChangeNotifier的状态,同时在初始化时调用加载方法:

    class PageA extends StatefulWidget {
      const PageA({super.key});
    
      @override
      State<PageA> createState() => _PageAState();
    }
    
    class _PageAState extends State<PageA> {
      @override
      void initState() {
        super.initState();
        // 调用加载方法,内部会自动判断是否需要请求数据
        Provider.of<MyNotifier>(context, listen: false).loadItems();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text("页面A")),
          body: Consumer<MyNotifier>(
            builder: (context, notifier, child) {
              if (notifier.isLoading) {
                return const Center(child: CircularProgressIndicator());
              } else if (notifier.errorMsg != null) {
                return Center(child: Text("出错了:${notifier.errorMsg}"));
              } else {
                return ListView.builder(
                  itemCount: notifier.items.length,
                  itemBuilder: (context, index) => ListTile(title: Text(notifier.items[index].name)),
                );
              }
            },
          ),
        );
      }
    }
    

    这样两个页面调用loadItems()时,只会触发一次数据请求,完美解决重复调用的问题。

为什么FutureProvider能避免重复调用?

FutureProvider内部会缓存创建的Future实例,当多个组件请求这个Provider时,它会返回同一个Future,而不是每次都执行create方法重新创建。不管多少个页面的FutureBuilder监听它,都只会触发一次数据请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:30:10