Kotlin中Firebase fetchArea返回空列表问题排查
使用Kotlin开发应用,基于Firebase存储和读取数据:
- 调用
saveArea()函数可正常存储数据 - 调用
fetchData()函数能正常获取指定数据 - 调用
fetchArea()获取全量用户数据时,返回空列表
Firestore控制台初始显示:
this document does not exist, it will not appear in queries or snapshot
手动点击「Add field」添加数据再删除后,控制台提示变为:
This document has no data.
此时fetchArea()恢复正常,可获取Users下所有数据。
附相关代码:
class FireBaseSourceImpl @Inject constructor(private val database: FirebaseFirestore) : FireBaseSource { override suspend fun saveArea(dataModel: DataModel): Result<Unit> { return kotlin.runCatching { val post = hashMapOf( "message" to dataModel.message, "title" to dataModel.title, "photoUrl" to dataModel.photoUrl, "timeStamp" to dataModel.timestamp ) database.collection("Users").document(dataModel.currentUser) .collection("construcitonName").document(dataModel.constructionArea).set(post) .await() } } override suspend fun fetchData( currentUser: String, constructionName: String ): Result<List<DataModel>> { return kotlin.runCatching { val dataModelList = mutableListOf<DataModel>() val documentSnapshot = database.collection("Users").document(currentUser).collection("construcitonName") .document(constructionName).get().await() if (documentSnapshot.exists()) { val data = documentSnapshot.data val message = data?.get("message") as? String ?: "" val title = data?.get("title") as? String ?: "" val photoUrl = data?.get("photoUrl") as? String ?: "" val timestamp = data?.get("timeStamp") as? Long ?: 0L val retrievedDataModel = DataModel( message, title, photoUrl, timestamp, currentUser, constructionName ) dataModelList.add(retrievedDataModel) } dataModelList.toList() } } override suspend fun fetchArea(): Result<List<ConstructionName>> { return kotlin.runCatching { val constructionNames = mutableListOf<ConstructionName>() val usersSnapshot = database.collection("Users") .get().await() if (usersSnapshot.isEmpty){ Log.d("FirebaseDebug", "usersSnapshot is empty") } for (userDocument in usersSnapshot.documents) { val userId = userDocument.id val constructionsSnapshot = userDocument.reference.collection("construcitonName").get().await() for (constructionDocument in constructionsSnapshot.documents) { val data = constructionDocument.data val message = data?.get("message") as? String ?: "" val title = data?.get("title") as? String ?: "" val photoUrl = data?.get("photoUrl") as? String ?: "" val timestamp = data?.get("timeStamp") as? Number ?: 0 val constructionName = ConstructionName( userId, constructionDocument.id, message, title, photoUrl, timestamp.toLong() ) constructionNames.add(constructionName) } } constructionNames.toList() } } }
核心原因
Firestore中,仅创建子集合但未给父文档写入任何字段的情况下,父文档属于「幽灵文档」(ghost document),这类文档不会出现在集合查询结果中。
你的saveArea()函数直接在Users/{currentUser}/construcitonName/{constructionArea}路径写入数据,相当于跳过了Users/{currentUser}父文档的显式创建,此时Users/{currentUser}就是幽灵文档,导致database.collection("Users").get()无法查询到该文档,最终fetchArea()返回空列表。
而手动添加字段再删除后,Users/{currentUser}变成了「空数据但存在的文档」,这类文档会被集合查询捕获,所以fetchArea()恢复正常。
解决方案
修改saveArea()函数,在写入子集合数据前,先显式创建父文档(即使父文档没有任何业务字段):
override suspend fun saveArea(dataModel: DataModel): Result<Unit> { return kotlin.runCatching { val post = hashMapOf( "message" to dataModel.message, "title" to dataModel.title, "photoUrl" to dataModel.photoUrl, "timeStamp" to dataModel.timestamp ) // 先显式创建用户文档(写入空数据即可) database.collection("Users").document(dataModel.currentUser).set(hashMapOf()) .await() // 再写入子集合数据 database.collection("Users").document(dataModel.currentUser) .collection("construcitonName").document(dataModel.constructionArea).set(post) .await() } }
如果需要给用户文档添加基础信息,也可以在创建时写入:
database.collection("Users").document(dataModel.currentUser).set( hashMapOf("createdAt" to FieldValue.serverTimestamp()) ).await()
补充说明
fetchData()能正常工作是因为它直接通过文档ID定位到子集合文档,不需要查询父集合,所以不受幽灵文档影响。- Firestore的集合查询只会返回存在的文档(包含空数据但已显式创建的文档),幽灵文档不会被包含在内。
内容的提问来源于stack exchange,提问作者Muhammed Ali ERKOÇ

