Flutter StreamBuilder空值异常:无法展示当前用户关联好友列表
Flutter StreamBuilder展示好友列表空指针异常修复方案
问题场景
在Flutter项目的Home页面中,通过嵌套StreamBuilder展示当前用户的关联好友列表时,触发"Null check operator used on a null value"异常。相关代码及异常信息如下:
页面StreamBuilder原代码
StreamBuilder<QuerySnapshot<Map<String, dynamic>>>( stream: controller.chatsStream(), builder: (context, snapshot1) { if(snapshot1.connectionState == ConnectionState.waiting){ return const Center( child: CircularProgressIndicator(), ); } else if (snapshot1.connectionState == ConnectionState.active) { var listDocsChats = snapshot1.data!.docs; print("List of Connections"); print(listDocsChats); return ListView.builder( padding: EdgeInsets.zero, itemCount: listDocsChats.length, itemBuilder: (context, index) { return StreamBuilder< DocumentSnapshot<Map<String, dynamic>>>( stream: controller .friendStream(listDocsChats[index]["connection"]), builder: (context, snapshot2) { if(snapshot2.connectionState == ConnectionState.waiting){ return const Center( child: CircularProgressIndicator(), ); } else { var data = snapshot2.data!.data(); return data!["status"] == "" ? ListTile( contentPadding: const EdgeInsets.symmetric( horizontal: 20, vertical: 5, ), onTap: (){}, leading: CircleAvatar( radius: 30, backgroundColor: Colors.black26, child: ClipRRect( borderRadius: BorderRadius.circular(100), child: data["photoUrl"] == "" ? Image.asset( "assets/images/219983.png", fit: BoxFit.cover, ) : Image.network( "${data["photoUrl"]}", fit: BoxFit.cover, ), ), ), title: Text( "${data["name"]}", style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w600, ), ), trailing: listDocsChats[index] ["total_unread"] == 0 ? const SizedBox() : Chip( backgroundColor: Colors.red[900], label: Text( "${listDocsChats[index]["total_unread"]}", style: const TextStyle( color: Colors.white), ), ), ) : ListTile( contentPadding: const EdgeInsets.symmetric( horizontal: 20, vertical: 5, ), onTap: (){}, leading: CircleAvatar( radius: 30, backgroundColor: Colors.black26, child: ClipRRect( borderRadius: BorderRadius.circular(100), child: data["photoUrl"] == "" ? Image.asset( "assets/images/219983.png", fit: BoxFit.cover, ) : Image.network( "${data["photoUrl"]}", fit: BoxFit.cover, ), ), ), title: Text( "${data["name"]}", style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w600, ), ), subtitle: Text( "${data["status"]}", style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, ), ), trailing: listDocsChats[index] ["total_unread"] == 0 ? const SizedBox() : Chip( backgroundColor: Colors.red[900], label: Text( "${listDocsChats[index]["total_unread"]}", style: const TextStyle( color: Colors.white), ), ), ); } }, ); }, ); }else{ return const Center( child: CircularProgressIndicator(), ); } }, );
HomeController原代码
class HomeController extends GetxController{ final auth = FirebaseAuth.instance.currentUser; chatsStream(){ return FirebaseFirestore.instance.collection("users"). doc(auth!.email).collection("chats") .orderBy("lastTime" , descending: true).snapshots(); } friendStream(String email){ FirebaseFirestore.instance.collection("users").doc(email).snapshots(); } }
异常信息
======== Exception caught by widgets library ======================================================= The following _CastError was thrown building StreamBuilder<DocumentSnapshot<Map<String, dynamic>>>(dirty, state: _StreamBuilderBaseState<DocumentSnapshot<Map<String, dynamic>>, AsyncSnapshot<DocumentSnapshot<Map<String, dynamic>>>>#3685c): Null check operator used on a null value The relevant error-causing widget was: StreamBuilder<DocumentSnapshot<Map<String, dynamic>>> StreamBuilder:file:///C:/Users/HP/Desktop/flutter/chat_app_newversion/lib /app/modules/home/view/contents/home_view_body.dart:30:24 When the exception was thrown, this was the stack: #0 HomeBody.build.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:chat_app_newversion/app/modules/home/view/contents/home_view_body.dart:41:48) #1 StreamBuilder.build (package:flutter/src/widgets/async.dart:442:81) #2 _StreamBuilderBaseState.build (package:flutter/src/widgets/async.dart:124:48) #3 StatefulElement.build (package:flutter/src/widgets/framework.dart:4919:27) #4 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4806:15) #5 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4977:11) #6 Element.rebuild (package:flutter/src/widgets/framework.dart:4529:5) #7 ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4787:5) #8 StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:4968:11) #9 ComponentElement.mount (package:flutter/src/widgets/framework.dart:4781:5) ... Normal element mounting (33 frames)
错误分析
- friendStream方法未返回Stream:HomeController中的
friendStream方法缺少return语句,默认返回null。嵌套的StreamBuilder接收null流后,snapshot2.data为null,使用!强制解包触发空指针异常。 - 未处理数据为空场景:即使friendStream返回正确流,若Firestore中无对应email的用户文档,
snapshot2.data!.data()会返回null,后续data!["status"]同样触发异常。 - 忽略错误状态:未处理Stream加载失败(如网络异常)的情况,导致异常无法被捕获。
修复步骤
1. 修复HomeController的friendStream方法
添加return语句并明确返回类型:
class HomeController extends GetxController{ final auth = FirebaseAuth.instance.currentUser; Stream<QuerySnapshot<Map<String, dynamic>>> chatsStream(){ return FirebaseFirestore.instance .collection("users") .doc(auth!.email) .collection("chats") .orderBy("lastTime", descending: true) .snapshots(); } Stream<DocumentSnapshot<Map<String, dynamic>>> friendStream(String email){ return FirebaseFirestore.instance .collection("users") .doc(email) .snapshots(); } }
2. 完善嵌套StreamBuilder的状态处理
添加错误判断、空数据处理,避免强制解包:
StreamBuilder<QuerySnapshot<Map<String, dynamic>>>( stream: controller.chatsStream(), builder: (context, snapshot1) { if(snapshot1.connectionState == ConnectionState.waiting){ return const Center(child: CircularProgressIndicator()); } if(snapshot1.hasError){ return Center(child: Text('加载会话列表失败: ${snapshot1.error}')); } if(!snapshot1.hasData || snapshot1.data!.docs.isEmpty){ return const Center(child: Text('暂无关联好友')); } var listDocsChats = snapshot1.data!.docs; return ListView.builder( padding: EdgeInsets.zero, itemCount: listDocsChats.length, itemBuilder: (context, index) { final connectionEmail = listDocsChats[index]["connection"]; if(connectionEmail == null || connectionEmail.isEmpty){ return const SizedBox.shrink(); } return StreamBuilder<DocumentSnapshot<Map<String, dynamic>>>( stream: controller.friendStream(connectionEmail), builder: (context, snapshot2) { if(snapshot2.connectionState == ConnectionState.waiting){ return const ListTile( leading: CircleAvatar(child: CircularProgressIndicator()), title: Text('加载中...'), ); } if(snapshot2.hasError){ return ListTile( title: const Text('加载好友信息失败'), subtitle: Text('错误: ${snapshot2.error}'), ); } if(!snapshot2.hasData || !snapshot2.data!.exists || snapshot2.data!.data() == null){ final unreadCount = listDocsChats[index]["total_unread"] ?? 0; return ListTile( title: const Text('好友信息不存在'), trailing: unreadCount > 0 ? Chip( backgroundColor: Colors.red[900], label: Text('$unreadCount', style: const TextStyle(color: Colors.white)), ) : null, ); } var data = snapshot2.data!.data()!; final hasStatus = data["status"] != null && data["status"].isNotEmpty; final hasPhoto = data["photoUrl"] != null && data["photoUrl"].isNotEmpty; final unreadCount = listDocsChats[index]["total_unread"] ?? 0; return ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5), onTap: (){}, leading: CircleAvatar( radius: 30, backgroundColor: Colors.black26, child: ClipRRect( borderRadius: BorderRadius.circular(100), child: hasPhoto ? Image.network(data["photoUrl"], fit: BoxFit.cover) : Image.asset("assets/images/219983.png", fit: BoxFit.cover), ), ), title: Text( data["name"] ?? '未知用户', style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w600), ), subtitle: hasStatus ? Text( data["status"], style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), ) : null, trailing: unreadCount > 0 ? Chip( backgroundColor: Colors.red[900], label: Text('$unreadCount', style: const TextStyle(color: Colors.white)), ) : const SizedBox(), ); }, ); }, ); }, );
关键优化点
- 明确指定Stream返回类型,避免隐式类型转换问题
- 新增错误状态处理,提升异常场景下的用户体验
- 校验所有可能为空的字段,用
??替代强制解包! - 合并重复的ListTile代码,减少冗余
- 过滤无效的connection字段,避免无效请求
内容的提问来源于stack exchange,提问作者FERHATI KHALIL
相关产品推荐
相关产品推荐

