使用Provider+FutureBuilder加载Firebase数据时build阶段调用setState报错如何解决?
错误原因
这个报错是因为你在Widget的build执行过程中触发了状态更新通知:
你直接在FutureBuilder的future参数中调用loadFollowers方法,该方法执行过程中会修改ProfileManager的状态并调用notifyListeners,而此时Widget还处于build流程中,状态更新会触发重绘,就会出现setState() or markNeedsBuild() called during build的错误。
另外你的loadFollowers方法存在异步逻辑漏洞:forEach中包裹的异步函数不会被等待,所有文档请求还没完成,方法就已经执行到loading = false并触发通知,数据加载逻辑本身也不符合预期。
修复步骤
1. 修复loadFollowers异步逻辑
将forEach异步遍历改为Future.wait等待所有请求完成,保证所有数据加载结束后再更新状态:
Future<void> loadFollowers(List profilesId) async { loading = true; notifyListeners(); // 批量发起所有文档请求,等待全部返回 final docList = await Future.wait( profilesId.map((id) => userRef.doc(id).get()) ); // 处理返回数据 followersList.clear(); for (var doc in docList) { followersList.add(Profile.fromDocument(doc)); } loading = false; notifyListeners(); }
2. 调整页面加载触发时机
将页面改为StatefulWidget,在initState的帧回调中触发加载逻辑,避免在build过程中更新状态,同时去掉冗余的FutureBuilder(你已经用Consumer监听状态变化,不需要额外用FutureBuilder):
// 你的页面改为StatefulWidget class FollowersPage extends StatefulWidget { final List followersId; const FollowersPage({super.key, required this.followersId}); @override State<FollowersPage> createState() => _FollowersPageState(); } class _FollowersPageState extends State<FollowersPage> { @override void initState() { super.initState(); // 等当前帧build完成后再触发加载,避免build过程中更新状态 WidgetsBinding.instance.addPostFrameCallback((_) { Provider.of<ProfileManager>(context, listen: false).loadFollowers(widget.followersId); }); } @override Widget build(BuildContext context) { return Stack( children: [ gradientBackground(), Scaffold( appBar: AppBar( brightness: Brightness.dark, elevation: 0, backgroundColor: Colors.transparent, centerTitle: true, title: const Text('Seguidores', style: TextStyle(color: Colors.white, fontFamily: 'MontSerrat')), ), backgroundColor: Colors.transparent, body: Consumer<ProfileManager>( builder: (_, profileManager, __) { if (profileManager.loading) { return circularProgress(Colors.white); } return ListView.builder( // 修复原来的取值错误,从profileManager取列表长度 itemCount: profileManager.followersList.length, itemBuilder: (context, index) { final follower = profileManager.followersList[index]; return GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (_) => ProfileScreen(home: false) ) ); }, child: Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: ListTile( leading: CircleAvatar( backgroundImage: CachedNetworkImageProvider(follower.picture ?? ''), ), title: Text( follower.name ?? '', style: const TextStyle(fontFamily: 'BebasNeue', fontSize: 20), ), ), ), ); }, ); }, ), ) ], ); } }
其他注意事项
如果你需要保留FutureBuilder的写法,不要直接在future参数中调用方法,需要提前在initState中初始化Future变量,再把变量传给FutureBuilder的future参数,避免每次build都重新执行加载逻辑。
内容的提问来源于stack exchange,提问作者Ricardo Oscar Kanitz
相关产品推荐
相关产品推荐

