使用SharedFlow从Room获取数据时持续返回null的问题
我在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,所以能正常返回。
修复步骤
- 让
employeeId成为可观察的Flow:确保employeeId是MutableStateFlow类型,比如:
val employeeId = MutableStateFlow<String?>(null) // 按实际类型调整
- 用
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确保新订阅能拿到最新数据
- DAO方法优化(推荐):因为ID是数据库主键,不可能为null,建议把DAO参数改为非空
Int,减少空值隐患:
@Query("SELECT * FROM employees WHERE id = :id") fun getEmployeeById(id: Int): Flow<EmployeeModel>
同步修改Repository接口和实现类的参数为非空Int。
- Fragment代码无需修改:原有的collect逻辑会自动接收ID变化后的新查询结果。
额外说明
flatMapLatest会在新ID到来时取消之前未完成的查询,确保只处理最新的用户操作。mapNotNull过滤无效的null ID,避免发起无意义的数据库请求。replay = 1可以让Fragment订阅时直接拿到最新的查询结果,不会错过数据。
内容的提问来源于stack exchange,提问作者SlipperyBarrel
相关产品推荐
相关产品推荐

