Flutter调用Cloud Firestore获取文档数据时触发不存在字段异常
问题核心根因
- 文档查询逻辑存在硬错误:调用
.doc()时未传入任何参数,无参doc()方法会自动生成一个随机的、未在数据库中存在的文档ID引用,拉取到的快照本身就是空的、不存在的,访问任意字段都会触发Bad state: cannot get a field on a DocumentSnapshotPlatform which does not exist异常。 - 字段解析没有做存在性判断:直接通过方括号语法取快照字段,一旦字段缺失、或者快照本身不存在,就会直接抛出错误,没有容错兜底。
- 数据类型不匹配:当前存储的
overview字段是字符串形式的"['a', 'b', 'c']",不是Firestore原生的数组结构,直接用List.from()转换会因为拿到的是字符串而转换失败。 - 组件流逻辑使用错误:没有把查询流绑定到组件状态中,每次组件重建都会新建流,点击按钮时调用
getDisease()没有实际触发UI刷新,错误、加载中、空数据的状态也没有做区分。
分步修正实现
1. 调整查询逻辑,匹配真实的数据库结构
如果需要拉取diseases集合下的所有文档,不要调用无参doc()方法,直接获取集合快照即可:
Stream<List<DiseaseRecord>> getDiseaseList() { return FirebaseFirestore.instance .collection("diseases") .snapshots() .map((collectionSnapshot) { // 过滤掉不存在的文档,避免取字段报错 return collectionSnapshot.docs .where((doc) => doc.exists) .map((doc) => DiseaseRecord.fromSnapshot(doc)) .toList(); }); }
如果需要拉取单个指定文档,必须把对应的文档ID作为参数传入doc()方法,不能留空:
// 替换为实际存在的文档ID Stream<DiseaseRecord> getSingleDisease(String docId) { return FirebaseFirestore.instance .collection("diseases") .doc(docId) .snapshots() .map((snapshot) { if (!snapshot.exists) return null; return DiseaseRecord.fromSnapshot(snapshot); }); }
2. 优化实体类解析逻辑,加容错处理
去掉没有兜底的直接字段访问,对不存在的快照、缺失的字段加默认值,同时兼容两种overview的存储格式:
class DiseaseRecord { String name; List<String> overview = []; DiseaseRecord.fromSnapshot(DocumentSnapshot snapshot) { final data = snapshot.data() as Map<String, dynamic>?; // 字段取不存在时给默认值,不直接抛错 name = data?['name'] ?? "未命名条目"; final rawOverview = data?['overview']; // 兼容原生数组格式 if (rawOverview is List) { overview = List<String>.from(rawOverview); } // 兼容当前字符串格式的列表 else if (rawOverview is String) { overview = rawOverview .replaceAll(RegExp(r"[\[\]']"), "") .split(",") .map((e) => e.trim()) .toList(); } } }
3. 修正组件的流绑定与状态判断
把查询流存在组件状态中,区分不同的快照状态,让按钮点击可以触发重新拉取:
class _FindDiseasesState extends State<FindDiseases> { // 将流绑定到组件状态,避免重建时重复新建 Stream<List<DiseaseRecord>>? diseaseStream; @override void initState() { super.initState(); // 页面初始化时加载数据 diseaseStream = getDiseaseList(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Colors.teal, automaticallyImplyLeading: false, title: const Text("Diseases List"), ), key: _scaffoldKey, body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ StreamBuilder<List<DiseaseRecord>>( stream: diseaseStream, builder: (context, snapshot) { // 单独处理报错场景 if (snapshot.hasError) { return Text("加载失败:${snapshot.error}"); } // 处理加载中场景 if (snapshot.connectionState == ConnectionState.waiting) { return const CircularProgressIndicator(); } // 处理空数据场景 if (!snapshot.hasData || snapshot.data!.isEmpty) { return const Text("暂无疾病记录"); } // 正常渲染数据 final recordList = snapshot.data!; return Column( children: recordList.map((e) => Text(e.name)).toList(), ); }, ), const SizedBox(height: 20), ElevatedButton( onPressed: () { // 点击时重新赋值流,触发刷新 setState(() { diseaseStream = getDiseaseList(); }); }, child: const Text("获取疾病记录"), ) ], ), ), ); } }
长期优化建议:把Firestore中的
overview字段直接存储为原生数组类型,不要序列化为字符串存储,后续解析不需要额外做字符串清洗,性能和稳定性都会更好。
内容的提问来源于stack exchange,提问作者Orion
相关产品推荐
相关产品推荐

