修改Stream Builder内的Future Builder以避免组件闪烁
解决StreamBuilder嵌套FutureBuilder导致UI闪烁的问题
一、可以在Activity流中一次性查询用户数据,移除嵌套的FutureBuilder
核心思路是通过switchMap将Activity的流转换为包含用户数据的组合数据流,一次性批量查询所有需要的用户,再关联到对应的Activity条目上,避免每个Item单独发起请求。
步骤1:定义组合数据模型
创建一个包含Activity和对应用户的模型,方便后续传递数据:
class ActivityWithUsers { final ActivityModel activity; final List<UserModel> users; ActivityWithUsers({required this.activity, required this.users}); }
步骤2:改造Activity流,合并用户数据
修改activityStream方法,先获取Activity快照,收集所有需要查询的用户UID(去重避免重复请求),批量查询用户后,将用户数据与对应的Activity关联:
Stream<List<ActivityWithUsers>> activityWithUsersStream() { return FirebaseFirestore.instance .collection('activity') .orderBy('timestamp', descending: true) .limit(55) .snapshots() .switchMap((activitySnapshot) async* { // 转换Activity数据 final activities = activitySnapshot.docs .map((doc) => ActivityModel.fromFirestore(doc)) .toList(); // 收集所有需要的用户UID并去重 final allUserUids = activities .expand((activity) => activity.players) .toSet() .toList(); if (allUserUids.isEmpty) { // 没有用户需要查询,直接返回空用户列表的Activity yield activities .map((activity) => ActivityWithUsers( activity: activity, users: [], )) .toList(); return; } // 批量查询用户数据 final userSnapshot = await FirebaseFirestore.instance .collection('users') .where('uid', whereIn: allUserUids) .get(); final users = userSnapshot.docs .map((doc) => UserModel.fromFirestore(doc)) .toList(); // 将用户数据关联到对应的Activity final activityWithUsersList = activities.map((activity) { final matchedUsers = users .where((user) => activity.players.contains(user.uid)) .toList(); return ActivityWithUsers( activity: activity, users: matchedUsers, ); }).toList(); yield activityWithUsersList; }); }
步骤3:重构UI层,移除嵌套的FutureBuilder
现在可以直接使用改造后的流,不需要每个Item单独发起请求:
StreamBuilder<List<ActivityWithUsers>>( stream: activityWithUsersStream(), builder: (context, snapshot) { if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } switch (snapshot.connectionState) { case ConnectionState.waiting: return const Center(child: CircularProgressIndicator()); default: final activityWithUsersList = snapshot.data ?? []; return Scrollbar( controller: widget.scrollController, child: ListView.builder( shrinkWrap: true, controller: widget.scrollController, itemCount: activityWithUsersList.length, itemBuilder: (context, i) { final item = activityWithUsersList[i]; // 直接使用已关联的用户数据 return MyWidget(users: item.users); }, ), ); } }, );
二、如果暂时无法改造数据流,可行的优化方案
1. 缓存用户查询结果,避免重复请求
每次查询用户后将结果缓存到内存或本地存储(比如Hive、SharedPreferences),下次需要时优先读取缓存,同时后台更新最新数据,等待时显示缓存内容而非空盒子:
// 内存缓存示例 final Map<String, UserModel> _userCache = {}; Future<List<UserModel>> getUsers(List<String> uids) async { // 先查缓存 final cachedUsers = uids .map((uid) => _userCache[uid]) .whereType<UserModel>() .toList(); final missingUids = uids.where((uid) => !_userCache.containsKey(uid)).toList(); if (missingUids.isEmpty) return cachedUsers; // 查询缺失的用户 final snapshot = await FirebaseFirestore.instance .collection('users') .where('uid', whereIn: missingUids) .get(); final newUsers = snapshot.docs.map((e) => UserModel.fromFirestore(e)).toList(); // 更新缓存 for (var user in newUsers) { _userCache[user.uid] = user; } return [...cachedUsers, ...newUsers]; }
2. 优化等待状态的UI,保持布局稳定
不要返回空的SizedBox,而是返回和MyWidget高度一致的骨架屏或加载状态,避免布局跳动:
if (snapshot.connectionState == ConnectionState.waiting) { return Container( height: 65.0, padding: EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ // 模拟头像骨架 Container( width: 40, height: 40, decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(20), ), ), SizedBox(width: 12), // 模拟文本骨架 Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 14, width: double.infinity, color: Colors.grey[200], margin: EdgeInsets.only(bottom: 6), ), Container( height: 12, width: 100, color: Colors.grey[200], ), ], ), ), ], ), ); }
3. 避免FutureBuilder重复发起请求
当前代码中,itemBuilder每次重建都会创建新的Future,导致频繁重复请求。可以将Future缓存到ActivityModel中,或者使用StatefulWidget存储Future:
// 示例:在ActivityModel中缓存用户查询Future class ActivityModel { // ...原有字段 List<String> players; Future<List<UserModel>>? _userFuture; Future<List<UserModel>> getUsers() { _userFuture ??= FirebaseFirestore.instance .collection('users') .where('uid', whereIn: players) .get() .then((snapshot) => snapshot.docs.map((e) => UserModel.fromFirestore(e)).toList()); return _userFuture!; } } // 然后在itemBuilder中使用 return FutureBuilder( future: activityContent[i].getUsers(), builder: (context, snapshot) { // ...原有逻辑 }, );
内容的提问来源于stack exchange,提问作者AlienDecoy
相关产品推荐
相关产品推荐

