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

使用SharedFlow从Room获取数据时持续返回null的问题

问题:通过SharedFlow根据ID查询数据库员工记录始终返回null(硬编码ID则正常)

我在ViewModel里用SharedFlow实现了根据ID从数据库获取单条员工记录的逻辑,ID是用户点击RecyclerView条目时设置的。现在遇到的问题是,不管怎么点,返回的都是null,但如果在Repository调用时硬写ID,功能完全正常。日志显示ViewModel里的ID值是正确的,和用户点击的条目对应。


DAO代码

@Query("SELECT * FROM employees WHERE id = :id")
fun getEmployeeById(id: Int?): Flow<EmployeeModel>

Repository接口

fun getEmployeeById(id: Int?): Flow<EmployeeModel>

Repository实现类

override fun getEmployeeById(id: Int?): Flow<EmployeeModel> {
    return employeeDAO.getEmployeeById(id)
}

ViewModel代码

var employeeById: SharedFlow<DatabaseState<EmployeeModel>> = repository.get().getEmployeeById(employeeId.value?.toInt())
    .map {
        println("onCreateView in VM. ID ${employeeId.value}  |  data: $it")
        DatabaseState.Success(it) }
    .catch { DatabaseState.Error(it.message) }
    .shareIn(viewModelScope, SharingStarted.WhileSubscribed(), replay = 0)

Fragment代码

viewLifecycleOwner.lifecycleScope.launch {
        repeatOnLifecycle(Lifecycle.State.STARTED) {
           mViewModel.employeeById.collect{ employee ->
               when (employee){
                   is DatabaseState.Success -> {
                       Log.i(TAG, "onCreateView APDEJCIK: ${mViewModel.employeeId.value} | ${employee.data}")
                   }
                   is DatabaseState.Error -> {
                       Log.i(TAG, "onCreateView: Failed to retrieve data about employee in UpdateFragmentEmployee fragment")}
               }
           }
        }
    }

补充:Model类代码

@Entity(tableName = "employees")
data class EmployeeModel(
    @PrimaryKey(autoGenerate = true)
    val id: Int,
    @ColumnInfo(name = "name")
    val name: String,
    @ColumnInfo(name = "surname")
    val surname: String,
    @ColumnInfo(name = "age")
    val age: Int,
    @ColumnInfo(name = "workplace")
    val workplace: String,
    @ColumnInfo(name = "salary")
    val salary: Double
)

问题原因及解决办法

核心问题

你的ViewModel中employeeById这个SharedFlow是一次性初始化的,只会在ViewModel创建时获取一次employeeId.value,之后用户点击RecyclerView更新employeeId时,这个SharedFlow并不会重新发起查询。所以实际执行查询时用的是ViewModel初始化时的employeeId值(大概率是null或默认值),自然查不到数据;而硬编码ID时,查询用的是固定有效ID,所以能正常返回。

修复步骤

  1. 让employeeId成为可观察的Flow:确保employeeId是MutableStateFlow类型,比如:
val employeeId = MutableStateFlow<String?>(null) // 按实际类型调整
  1. 用flatMapLatest监听ID变化并发起新查询:修改ViewModel中的employeeById定义,通过flatMapLatest响应ID变化,自动触发新查询:
val employeeById: SharedFlow<DatabaseState<EmployeeModel>> = employeeId
    .mapNotNull { it?.toInt() } // 过滤null,只处理有效ID
    .flatMapLatest { id ->
        repository.get().getEmployeeById(id)
            .map { DatabaseState.Success(it) }
            .catch { emit(DatabaseState.Error(it.message)) }
    }
    .shareIn(viewModelScope, SharingStarted.WhileSubscribed(), replay = 1) // 调整replay确保新订阅能拿到最新数据
  1. DAO方法优化(推荐):因为ID是数据库主键,不可能为null,建议把DAO参数改为非空Int,减少空值隐患:
@Query("SELECT * FROM employees WHERE id = :id")
fun getEmployeeById(id: Int): Flow<EmployeeModel>

同步修改Repository接口和实现类的参数为非空Int。

  1. Fragment代码无需修改:原有的collect逻辑会自动接收ID变化后的新查询结果。

额外说明

  • flatMapLatest会在新ID到来时取消之前未完成的查询,确保只处理最新的用户操作。
  • mapNotNull过滤无效的null ID,避免发起无意义的数据库请求。
  • replay = 1可以让Fragment订阅时直接拿到最新的查询结果,不会错过数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 11:35:07