Flutter使用Provider时build期间调用setState报错的解决方法
Flutter Provider 报错:setState() or markNeedsBuild() called during build
错误信息
The following assertion was thrown while dispatching notifications for CampaignProvider: setState() or markNeedsBuild() called during build. This _InheritedProviderScope<CampaignProvider?> widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase. The widget on which setState() or markNeedsBuild() was called was: _InheritedProviderScope<CampaignProvider?> The widget which was currently being built when the offending call was made was: KeyedSubtree-[GlobalKey#a8154]
组件代码
class CampaignsScreen extends StatefulWidget { const CampaignsScreen({Key? key}) : super(key: key); @override State<CampaignsScreen> createState() => _CampaignsScreenState(); } class _CampaignsScreenState extends State<CampaignsScreen> { @override void initState() { super.initState(); context.read<CampaignProvider>().getCampaigns(); } @override Widget build(BuildContext context) { List<Campaign> campaigns = context.watch<CampaignProvider>().campaigns; return Scaffold( appBar: AppBar( title: const Text('Campagnes'), ), body: Center( child: ListView.builder( itemCount: campaigns.length, itemBuilder: (BuildContext context, int index) { return ListTile( title: Text(campaigns[index].title), subtitle: Text(campaigns[index].description), ); }, ), ), ); } }
Provider代码
class CampaignProvider with ChangeNotifier { final CampaignService _campaignService = CampaignService(); List<Campaign> _campaigns = []; List<Campaign> get campaigns { return _campaigns; } void getCampaigns() { _campaigns = _campaignService.getCampaigns(); notifyListeners(); } }
问题分析与解决方案
错误根源是在组件构建阶段(initState执行时)同步调用了notifyListeners(),此时框架正在构建widget树,触发状态更新会导致框架尝试标记组件重建,引发冲突。
以下是几种可行的解决方法:
方法1:使用addPostFrameCallback
将getCampaigns()的调用延迟到当前帧构建完成后执行:
@override void initState() { super.initState(); // 等待当前帧构建完成后再执行 WidgetsBinding.instance.addPostFrameCallback((_) { context.read<CampaignProvider>().getCampaigns(); }); }
方法2:使用Future.delayed(Duration.zero)
通过延迟到下一个事件循环执行,避开当前构建阶段:
@override void initState() { super.initState(); Future.delayed(Duration.zero, () { context.read<CampaignProvider>().getCampaigns(); }); }
方法3:适配异步数据请求(推荐)
如果实际场景中getCampaigns是异步操作(比如网络请求),需要将Provider方法改为异步,并在initState中异步调用:
修改Provider代码:
class CampaignProvider with ChangeNotifier { final CampaignService _campaignService = CampaignService(); List<Campaign> _campaigns = []; List<Campaign> get campaigns { return _campaigns; } // 改为异步方法 Future<void> getCampaigns() async { _campaigns = await _campaignService.getCampaigns(); notifyListeners(); } }
修改组件initState:
@override void initState() { super.initState(); _fetchCampaigns(); } // 异步调用避免阻塞构建 void _fetchCampaigns() async { await context.read<CampaignProvider>().getCampaigns(); }
补充说明
替换context.watch()为Consumer无效的原因:错误的核心是notifyListeners()的调用时机在构建过程中,而非监听方式的问题。只要把状态更新的触发时机移到构建完成后,无论用watch还是Consumer都能正常工作。
内容的提问来源于stack exchange,提问作者iamflubin
相关产品推荐
相关产品推荐

