You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)

错误分析

  1. friendStream方法未返回Stream:HomeController中的friendStream方法缺少return语句,默认返回null。嵌套的StreamBuilder接收null流后,snapshot2.data为null,使用!强制解包触发空指针异常。
  2. 未处理数据为空场景:即使friendStream返回正确流,若Firestore中无对应email的用户文档,snapshot2.data!.data()会返回null,后续data!["status"]同样触发异常。
  3. 忽略错误状态:未处理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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 21:01:04