Flutter如何在initState中调用依赖StreamBuilder数据的Future
问题原因
snapshot是StreamBuilder的builder方法的局部参数,index是ListView.builder的itemBuilder的局部参数,二者的作用域仅在对应的builder函数内部,外部定义的getNewUid方法和initState根本访问不到这两个变量,所以会标红报错。- initState的执行时机远早于StreamBuilder拿到数据的时间,就算能解决作用域问题,initState执行时StreamBuilder的snapshot也还是空值,根本拿不到数据。
- 你尝试修改的
initState async写法是错误的,框架原生的initState是同步方法,不能标记为async,运行会直接报错。
解决方法
你不需要把Future抽去initState,只要加一层缓存即可解决滚动重复请求的问题:
- 首先在你的State类中定义一个缓存Map,用来存储已经请求过的uid对应的用户数据Future
// 定义在State类的顶部,和initState同级 Map<String, Future<DocumentSnapshot>> _userCache = {};
- 修改ListView的itemBuilder逻辑,优先读缓存,不存在再发起请求:
body: StreamBuilder( stream: BusiPosts.orderBy('time', descending: true).snapshots(), builder: (BuildContext context, AsyncSnapshot<dynamic> postSnapshot) { if (postSnapshot.hasError) { return Text('Something went wrong'); } if (postSnapshot.connectionState == ConnectionState.waiting) { return Center( child: CircularProgressIndicator() ); } return ListView.builder( itemCount: postSnapshot.data.docs.length, itemBuilder: (context, index) { // 拿到当前列表项对应的用户uid String uid = postSnapshot.data.docs[index]['uid']; // 缓存中没有该uid的请求记录,就新建请求存入缓存 if (!_userCache.containsKey(uid)) { CollectionReference Users = FirebaseFirestore.instance.collection('Users'); _userCache[uid] = Users.doc(uid).get(); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ ListTile( title: FutureBuilder<DocumentSnapshot>( // 直接使用缓存中的Future,不会重复发起请求 future: _userCache[uid], builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> userSnapshot) { if (userSnapshot.hasError) { return Text("Something went wrong"); } if (userSnapshot.hasData && !userSnapshot.data!.exists) { return Text("Document does not exist"); } if (userSnapshot.connectionState == ConnectionState.done) { Map<String, dynamic> data = userSnapshot.data!.data() as Map<String, dynamic>; return GestureDetector( // 注意这里原来的data[index]是错误写法,data就是当前用户的单条数据 onTap: () => navigateToDetail(data), // 剩余你的布局逻辑 ); } return CircularProgressIndicator(); }, ), ) ], ); }, ); } )
该方案同一个uid只会发起一次请求,就算列表滚动重建也是直接读取缓存的Future,完美解决你遇到的重复触发请求的问题,同时避开了变量作用域和初始化时机错误的问题。
注意事项
- 外层StreamBuilder的snapshot和内层FutureBuilder的snapshot不要重名,避免取值混淆
- 不需要保留你原来写的外部getNewUid方法和initState里的相关调用代码,直接删除即可
内容的提问来源于stack exchange,提问作者kernelrpc_mach_vm_protect
相关产品推荐
相关产品推荐

