Flutter中使用StreamBuilder实现抽屉好友请求列表时无限加载问题
问题:Drawer中StreamBuilder一直显示无限加载进度指示器
我想在Drawer里用StreamBuilder展示所有好友请求数据,但无论是用StreamBuilder还是FutureBuilder,界面始终停留在圆形进度指示器的加载状态,找不到问题根源。
抽屉组件代码
class FriendsRequestDrawer extends StatefulWidget { static const String routeName = "friends_request_drawer"; const FriendsRequestDrawer({super.key}); @override State<FriendsRequestDrawer> createState() => _FriendsRequestDrawerState(); } class _FriendsRequestDrawerState extends State<FriendsRequestDrawer> { @override Widget build(BuildContext context) { final userProvider = Provider.of<UserProvider>(context); return StreamBuilder<List<UserAccount>>( stream: userProvider.getFindsRequestsAccountsStream(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Center(child: CircularProgressIndicator()); } if (snapshot.hasError) { return Center(child: Text("Error: ${snapshot.error}")); } final userAccount = snapshot.data ?? []; if (userAccount.isEmpty) { return Center(child: Text("No Find Requests")); } return Drawer( child: ListView.builder( itemCount: userAccount.length, itemBuilder: (context, index) { return ListTile( leading: AvatarLogo( onPressed: () {}, width: 30, height: 30, border: null, backgroundImage: userAccount[index].photoURL == null ? null : AssetImage(userAccount[index].photoURL!), ), title: Text(userAccount[index].pseudo), ); }, ), ); }, ); } }
相关Stream逻辑代码
Stream<List<UserAccount>> getFindsRequestsAccountsStream() async* { if (userAccount != null && userAccount!.findsRequest.isNotEmpty) { List<UserAccount> findsRequestsAccounts = []; for (Map<String, String> element in userAccount!.findsRequest) { try { UserAccount newUser = await DbHelper.getStreamUser(element['uid']!).first; findsRequestsAccounts.add(newUser); } catch (e) { print('Error fetching user: $e'); } } yield findsRequestsAccounts; } else { yield []; } } static Stream<UserAccount> getStreamUser(String uid) { return FirebaseFirestore.instance .collection(collectionUser) .doc(uid) .snapshots() .map((snapshot) => UserAccount.fromJson(snapshot.data()!)); }
问题排查与解决方案
1. 核心问题:Stream未正常发出数据
进度指示器一直显示,说明StreamBuilder始终未接收到Stream发出的任何数据,大概率是getFindsRequestsAccountsStream中的异步操作卡住了:
- Firestore权限/文档不存在导致阻塞:
DbHelper.getStreamUser(element['uid']!).first会等待目标用户的第一个快照,如果该用户文档不存在(或者无读取权限),snapshots()可能会一直处于等待状态,既不抛出异常也不返回数据,导致整个Stream无法执行到yield步骤。 - 未处理快照为空的情况:
getStreamUser中直接调用snapshot.data()!,如果文档不存在,snapshot.data()为null,会触发空指针异常;若异常未被正确捕获(或Firestore静默等待),也会阻塞流程。
2. 针对性修复步骤
(1)给异步操作添加超时,避免无限等待
修改getFindsRequestsAccountsStream中的await逻辑,添加超时时间,防止单个用户请求卡住整个流程:
UserAccount newUser = await DbHelper.getStreamUser(element['uid']!).first .timeout(const Duration(seconds: 5), onTimeout: () { throw Exception('Fetch user ${element['uid']} timeout'); });
(2)处理Firestore快照为空的情况
修改getStreamUser,提前判断文档是否存在,避免空指针并明确抛出异常:
static Stream<UserAccount> getStreamUser(String uid) { return FirebaseFirestore.instance .collection(collectionUser) .doc(uid) .snapshots() .map((snapshot) { if (!snapshot.exists) { throw Exception('User document with uid $uid not found'); } final data = snapshot.data(); if (data == null) { throw Exception('User document $uid has no data'); } return UserAccount.fromJson(data); }); }
(3)优化Stream的实时性(可选)
当前的Stream只会发出一次数据,无法监听好友请求列表或用户信息的后续变化。如果需要实时更新,可以改为监听userAccount的变化,并合并所有用户的实时Stream:
Stream<List<UserAccount>> getFindsRequestsAccountsStream() { // 监听userAccount的变化(假设userAccount是Provider中的可监听状态) return Stream.value(userAccount).asyncExpand((currentUser) async* { if (currentUser == null || currentUser.findsRequest.isEmpty) { yield []; return; } // 合并所有用户的实时Stream final userStreams = currentUser.findsRequest.map((element) { return DbHelper.getStreamUser(element['uid']!) .catchError((e) { print('Error fetching user ${element['uid']}: $e'); return UserAccount.empty(); // 返回空对象避免中断整个流 }); }); // 当任一用户信息更新时,重新组合列表 yield* CombineLatestStream.list(userStreams); }); }
(4)完善StreamBuilder的状态判断
在StreamBuilder中同时判断连接状态和数据是否存在,避免遗漏边界情况:
if (snapshot.connectionState == ConnectionState.waiting || !snapshot.hasData) { return Center(child: CircularProgressIndicator()); }
内容的提问来源于stack exchange,提问作者Julien
相关产品推荐
相关产品推荐

