Flutter使用Provider调用API时出现“setState() or markNeedsBuild() called during build”错误的原因及解决方法
Flutter Provider调用API时setState()/markNeedsBuild()错误的解决办法
咱们先来拆解一下你遇到的这个错误,再一步步解决它。
错误原因
这个错误的核心是:你在Widget的构建流程还没结束的时候,触发了Provider的notifyListeners()方法。Flutter的框架规则很明确——当正在构建Widget树的时候,不能随便标记组件需要重新构建,否则会打乱框架的构建顺序。
具体到你的代码里:
- 你在
HomePage的initState方法里直接调用了Provider的getPostData,而initState本身是Widget构建阶段的一个环节。 getPostData里的异步API请求完成后,马上调用了notifyListeners(),这时候HomePage的build方法可能还在执行中,框架正处于构建这个页面的流程里,突然收到重新构建的通知,就会抛出这个断言错误。- 另外你代码里还有个小bug:
getPostData里的判断逻辑写反了——if (this._items.isNotEmpty)意味着只有当已有数据的时候才去请求新数据,这和你的需求完全相反,一开始_items是空的,永远不会触发API请求。
解决方法
这里给你三种可行的解决思路,你可以根据自己的习惯选择:
方法1:延迟到当前帧构建完成后再请求数据
最简单的方式是把initState里的Provider调用,放到addPostFrameCallback中,这样会等当前页面的build流程完全结束后,再去执行API请求和后续的通知:
@override void initState() { super.initState(); // 等到当前帧构建完成后再执行 WidgetsBinding.instance.addPostFrameCallback((_) { Provider.of<AlbumProvider>(context, listen: false).getPostData(); }); }
方法2:修正Provider逻辑,确保通知时机正确
我们先把Provider里的逻辑漏洞补上,再保证notifyListeners()在安全的时机调用:
class AlbumProvider with ChangeNotifier { List<Album> _items = []; bool _loading = true; // 把loading状态放到Provider里统一管理 // 对外暴露数据和状态的getter List<Album> get allitems => _items; bool get loading => _loading; Future<void> getPostData() async { // 修正逻辑:只有当数据为空时才请求 if (_items.isEmpty) { try { _items = await fetchAlbum(); } catch (e) { print('请求出错:$e'); } } _loading = false; // 等当前帧结束后再通知组件更新,避免和构建流程冲突 await SchedulerBinding.instance.endOfFrame; notifyListeners(); } }
同时修正fetchAlbum里的笔误(你之前写了Anime.fromJson,应该是Album.fromJson),并且去掉不需要的context参数:
String albumUrl = "http://192.168.227.102:9000/static/album.json"; Future<List<Album>> fetchAlbum() async { List<Album> resAlbum = []; try { final response = await http.get(Uri.parse(albumUrl)); if (response.statusCode == 200) { List<dynamic> result = jsonDecode(response.body)['album']; // 用map简化代码,避免forEach循环 resAlbum = result.map((tx) => Album.fromJson(tx as Map<String, dynamic>)).toList(); } } catch (e) { print(e); } return resAlbum; }
最后修改HomePage的build方法,利用Provider里的loading状态做更友好的展示:
@override Widget build(BuildContext context) { final albumProvider = Provider.of<AlbumProvider>(context); return Scaffold( appBar: AppBar(title: Text('Home Screen')), body: albumProvider.loading ? const Center(child: Text("正在获取数据...")) : albumProvider.allitems.isEmpty ? const Text("暂无数据") : const Text("数据获取完成"), ); }
方法3:使用FutureBuilder配合Provider(可选)
如果你不想在Provider里管理loading状态,也可以在页面中用FutureBuilder来处理异步请求,但结合Provider的状态管理模式,更推荐把数据和状态统一放到Provider中,这样整个应用都能方便地访问这些数据。
内容的提问来源于stack exchange,提问作者Gagan Singh
相关产品推荐
相关产品推荐

