Firestore泛型仓库问题:Map替代POJO查询后类型信息丢失求助
泛型Firestore仓库Map操作的类型丢失问题
问题描述
我正在实现一个针对Firestore集合的泛型仓库,使用Map而非POJO进行数据操作。目前已完成一个函数,可从指定文档中仅检索所需字段,将字段转换为期望类型后,将新Map封装在RequestState密封类中返回。但在函数外部使用返回结果时遇到问题:必须将结果强制转换为Map<String, Any>,导致所有类型信息丢失。
现有代码
sealed class RequestState(val result: Any?) { class Error(exception: Exception) : RequestState(exception) class Loading() : RequestState(null) class Success(data: Any) : RequestState(data) } override suspend fun retrieveFields( documentName: String, fields: List<String>, expectedType: List<KClass<*>> ): Flow<RequestState> = callbackFlow { try { // 检查fields或expectedType是否为空 if (fields.isEmpty() || expectedType.isEmpty()) throw EmptyArgument(requestName = "Retrieve Fields") // 检查fields和expectedType的大小是否一致 if (fields.size != expectedType.size) throw InvalidSize( requestName = "Retrieve Fields", cause = "参数数量不匹配" ) trySend(RequestState.Loading()) // 获取指定名称的文档 this@CollectionRepositoryImpl.collection.document(documentName).get() .addOnFailureListener { exception -> // 处理失败并关闭流 trySend(RequestState.Error(exception)) close() }.addOnSuccessListener { document -> // 创建可变Map存储获取到的字段 val resultMap = mutableMapOf<String, Any>() document.data?.let { map -> // 遍历字段和期望类型 fields.onEachIndexed { index, fieldName -> if (fieldName in map.keys) { // 存储字段及其期望类型到resultMap val data = document to expectedType[index] resultMap[fieldName] = data } else { Log.w( "Test", "文档$documentName中未找到字段$fieldName" ) throw NoSuchField(fieldName, "Retrieve Fields") } } } // 发送成功结果并关闭流 trySend(RequestState.Success(resultMap)) close() } awaitClose() } catch (e: Exception) { // 处理异常并关闭流 trySend(RequestState.Error(e)) close() } }
解决方案
1. 泛型改造RequestState密封类
让密封类支持泛型,使Success状态能携带具体类型信息,避免后续强制转换丢失类型:
sealed class RequestState<out T> { data class Error(val exception: Exception) : RequestState<Nothing>() object Loading : RequestState<Nothing>() data class Success<out T>(val data: T) : RequestState<T>() }
将result字段拆分到各个子类,既符合密封类的职责单一设计,又通过泛型明确了数据类型。
2. 优化retrieveFields函数的类型安全
原代码存在两个问题:一是用两个独立列表传递字段和类型,容易出现索引匹配错误;二是错误地将DocumentSnapshot与类型配对存储,而非字段值。以下是修正后的泛型版本:
override suspend fun <T : Map<String, *>> retrieveFields( documentName: String, fieldTypeMap: Map<String, KClass<*>> ): Flow<RequestState<T>> = callbackFlow { try { if (fieldTypeMap.isEmpty()) throw EmptyArgument(requestName = "Retrieve Fields") trySend(RequestState.Loading) this@CollectionRepositoryImpl.collection.document(documentName).get() .addOnFailureListener { exception -> trySend(RequestState.Error(exception)) close() }.addOnSuccessListener { document -> val resultMap = mutableMapOf<String, Any>() document.data?.let { docData -> fieldTypeMap.forEach { (fieldName, expectedType) -> val value = docData[fieldName] ?: run { Log.w("Test", "文档$documentName中未找到字段$fieldName") throw NoSuchField(fieldName, "Retrieve Fields") } // 校验字段类型是否符合预期 if (expectedType.isInstance(value)) { resultMap[fieldName] = value } else { throw IllegalArgumentException( "字段$fieldName类型不匹配:期望${expectedType.simpleName},实际${value::class.simpleName}" ) } } } ?: throw IllegalStateException("文档$documentName不存在或无数据") @Suppress("UNCHECKED_CAST") trySend(RequestState.Success(resultMap as T)) close() } awaitClose() } catch (e: Exception) { trySend(RequestState.Error(e)) close() } }
- 用
Map<String, KClass<*>>替代两个列表,确保字段与类型一一对应,避免索引错误 - 提取文档中的字段值并校验类型,确保存入Map的是符合预期类型的数据
- 泛型返回
Flow<RequestState<T>>,调用时可明确指定Map的类型
3. 调用示例
现在调用时无需强制转换,直接获取带有类型约束的Map:
val fieldTypeMap = mapOf( "username" to String::class, "age" to Int::class, "isVerified" to Boolean::class ) viewModelScope.launch { repository.retrieveFields<Map<String, Any>>("user_123", fieldTypeMap) .collect { state -> when (state) { RequestState.Loading -> // 显示加载UI is RequestState.Error -> // 处理异常,如Toast提示 is RequestState.Success -> { // 直接使用state.data,类型为Map<String, Any>,且字段类型已校验 val username = state.data["username"] as String val age = state.data["age"] as Int // 业务逻辑处理 } } } }
内容的提问来源于stack exchange,提问作者MCDragusanu
相关产品推荐
相关产品推荐

