Flutter中如何让两个FutureBuilder等待同一个Future?
这个场景我太熟悉了!之前做项目时也碰到过两个页面重复请求同一份数据的问题,FutureProvider绝对是可行且非常合适的解决方案,另外还有一种基于你现有ChangeNotifier的方案也能搞定,我给你详细拆解:
这是最直接的方案,FutureProvider的核心作用就是在组件树中共享一个Future的状态,所有依赖它的子组件(不管是哪个页面)都会复用同一个Future实例,从根源上避免重复调用。
具体步骤:
准备依赖和基础代码
先确保你在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")]; }在共同父组件注入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(), // 这里是包含两个目标页面的入口 ), ), ), ); }在页面中复用共享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,只会触发一次数据请求。
如果你更习惯用原来的ChangeNotifierProvider,也可以把Future的执行逻辑封装到ChangeNotifier里,确保只执行一次:
具体步骤:
修改你的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(); } } }在页面中监听状态
不用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内部会缓存创建的Future实例,当多个组件请求这个Provider时,它会返回同一个Future,而不是每次都执行create方法重新创建。不管多少个页面的FutureBuilder监听它,都只会触发一次数据请求。
内容的提问来源于stack exchange,提问作者LateBoy

