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

StreamBuilder传入DocumentSnapshot流时Snapshot返回null数据问题

Flutter StreamBuilder 加载单个DocumentSnapshot流时数据始终为null的问题

在Flutter开发中,使用StreamBuilder监听Firebase Firestore单个DocumentSnapshot的流时,snapshot始终返回null数据。已添加连接状态与数据检查,但界面一直显示“No Profile Data”。相关代码如下:

UserModel.dart

import 'package:cloud_firestore/cloud_firestore.dart';

class UserModel {
 String? earningsUnit;
 String? email;
 String? fullName;
 String? lastUpdate;
 String? profileImageUrl;
 String? superAdmin;
 String? totalPoints;
 String? totalWorkMinutes;
 String? uid;
 String? updatedBy;

 UserModel({
  this.earningsUnit,
  this.email,
  this.fullName,
  this.lastUpdate,
  this.profileImageUrl,
  this.superAdmin,
  this.totalPoints,
  this.totalWorkMinutes,
  this.uid,
  this.updatedBy,
 });

factory UserModel.fromDocumentSnapshot(Map<String,dynamic> data) {
 return UserModel(
   earningsUnit:
       data.containsKey("earningsUnit") ? data["earningsUnit"] ?? "" : "",
   email: data.containsKey("email") ? data["email"] ?? "" : "",
   fullName: data.containsKey("fullname") ? data["fullname"] ?? "" : "",
   lastUpdate:
       data.containsKey("lastupdate") ? data["lastupdate"] ?? false : false,
   profileImageUrl: data.containsKey("profileImageUrl")
       ? data["profileImageUrl"] ?? 0
       : 0,
   superAdmin: data.containsKey("superAdmin") ? data["superAdmin"] ?? 0 : 0,
   totalPoints:
      data.containsKey("totalPoints") ? data["totalPoints"] ?? 0 : 0,
   totalWorkMinutes: data.containsKey("totalWorkMinutes")
       ? data["totalWorkMinutes"] ?? ""
       : "",
   uid: data.containsKey("uid") ? data["uid"] ?? "" : "",
   updatedBy: data.containsKey("updatedby") ? data["updatedby"] ?? "" : "",
  );
 }
}

UserNetwork.dart

class UserNetwork {
  final FirebaseFirestore _db = FirebaseFirestore.instance;

  Stream<UserModel> retrieveUserData(String id) {
    final stream = _db.collection('users').doc(id).snapshots();

    return stream.map((snap) => UserModel.fromDocumentSnapshot(snap.data()!));
  }
}

StreamBuilder实现

StreamBuilder(
  stream: userNetwork
      .retrieveUserData(FirebaseAuth.instance.currentUser!.uid),
  builder: (BuildContext context, snapshot) {
    if (snapshot.connectionState == ConnectionState.active) {
      if (snapshot.data != null) {
        return Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Container(
              padding: const EdgeInsets.fromLTRB(20, 0, 0, 0),
              child: Text(
                snapshot.data!.fullName ?? "",
                style: const TextStyle(
                  fontWeight: FontWeight.w900,
                  fontSize: 25,
                ),
              ),
            ),
            InkWell(
              onTap: () {
                Navigator.pushNamed(context, EditProfile.id);
              },
              child: Container(
                padding: const EdgeInsets.fromLTRB(0, 0, 20, 0),
                child: CircleAvatar(
                  backgroundImage:
                      NetworkImage(snapshot.data!.profileImageUrl!),
                  radius: 40,
                ),
              ),
            ),
          ],
        );
      } else {
        return Center(
          child: Text(
            'No Profile Data',
            style: cTextStyleMedium,
          ),
        );
      }
    }

    return Center(
      child: CustomLoading(),
    );
  },
),

界面显示No Profile Data


问题排查与解决方案

1. 修复DocumentSnapshot强制解包风险

在UserNetwork的retrieveUserData方法中,直接使用snap.data()!强制解包会在文档不存在或加载失败时抛出异常,导致流中断。需先判断文档是否存在:

Stream<UserModel?> retrieveUserData(String id) {
  final stream = _db.collection('users').doc(id).snapshots();
  return stream.map((snap) {
    if (snap.exists) {
      return UserModel.fromDocumentSnapshot(snap.data()!);
    } else {
      return null; // 文档不存在时返回null
    }
  });
}

2. 修正UserModel类型不匹配错误

UserModel工厂构造函数中存在多处类型不匹配问题,会导致模型初始化失败:

  • lastUpdate为String类型,却设置布尔值false为默认值
  • profileImageUrl、superAdmin、totalPoints为String类型,却设置数字0为默认值

修正后的工厂构造函数:

factory UserModel.fromDocumentSnapshot(Map<String,dynamic> data) {
  return UserModel(
    earningsUnit: data.containsKey("earningsUnit") ? data["earningsUnit"] ?? "" : "",
    email: data.containsKey("email") ? data["email"] ?? "" : "",
    fullName: data.containsKey("fullname") ? data["fullname"] ?? "" : "",
    lastUpdate: data.containsKey("lastupdate") ? data["lastupdate"] ?? "" : "",
    profileImageUrl: data.containsKey("profileImageUrl") ? data["profileImageUrl"] ?? "" : "",
    superAdmin: data.containsKey("superAdmin") ? data["superAdmin"] ?? "" : "",
    totalPoints: data.containsKey("totalPoints") ? data["totalPoints"] ?? "" : "",
    totalWorkMinutes: data.containsKey("totalWorkMinutes") ? data["totalWorkMinutes"] ?? "" : "",
    uid: data.containsKey("uid") ? data["uid"] ?? "" : "",
    updatedBy: data.containsKey("updatedby") ? data["updatedby"] ?? "" : "",
  );
}

3. 完善StreamBuilder异常处理

当前代码未处理流中的异常(如权限不足、网络错误),需添加错误捕获逻辑:

builder: (BuildContext context, snapshot) {
  if (snapshot.hasError) {
    return Center(child: Text('加载失败: ${snapshot.error}'));
  }

  if (snapshot.connectionState == ConnectionState.active) {
    if (snapshot.data != null) {
      return Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Container(
            padding: const EdgeInsets.fromLTRB(20, 0, 0, 0),
            child: Text(
              snapshot.data!.fullName ?? "",
              style: const TextStyle(
                fontWeight: FontWeight.w900,
                fontSize: 25,
              ),
            ),
          ),
          InkWell(
            onTap: () {
              Navigator.pushNamed(context, EditProfile.id);
            },
            child: Container(
              padding: const EdgeInsets.fromLTRB(0, 0, 20, 0),
              child: CircleAvatar(
                backgroundImage: NetworkImage(snapshot.data!.profileImageUrl ?? ""),
                radius: 40,
              ),
            ),
          ),
        ],
      );
    } else {
      return Center(
        child: Text(
          'No Profile Data',
          style: cTextStyleMedium,
        ),
      );
    }
  }

  return Center(child: CustomLoading());
},

4. 额外检查项

  • 确认Firebase Auth的currentUser不为null,避免强制解包报错:
    final currentUser = FirebaseAuth.instance.currentUser;
    if (currentUser == null) {
      return Text('请先登录');
    }
    // 再使用currentUser.uid调用retrieveUserData
    
  • 验证Firestore中users集合存在对应UID的文档,且字段名与代码中一致(如fullname是小写)
  • 确认Firestore权限规则允许当前用户读取该文档

内容的提问来源于stack exchange,提问作者Shahroz Javed

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 07:25:21