使用Room+Flow获取产品后删除引发空指针异常的解决咨询
问题分析与解决方案
核心问题
你遇到的空指针异常,根源是Room的查询特性:当@Query返回单个实体的Flow<ProductEntity>时,若对应ID的数据被删除,Flow不会终止,而是会发射null值。而你的Repository中直接对这个Flow调用map{it.toDomain()},此时it为null,调用toDomain()就触发了空指针。同时ViewModel中用viewModelScope.launch持续collect这个Flow,即使数据删除后仍在监听,导致错误持续触发。
解决方案
1. 修正DAO与Repository,处理null情况
首先修改DAO,让返回的Flow允许null:
@Dao interface ProductDAO { // ... 其他方法不变 @Query("SELECT * FROM productEntity WHERE id=:id") fun getProductById(id: Int): Flow<ProductEntity?> // 改为可空类型 }
然后在Repository中处理null,同时修正代码笔误(contatoDAO应为productDAO):
class ProductRepository(private val productDAO: ProductDAO) { suspend fun insert(product: ProductEntity){ productDAO.insert(product) // 修正笔误 } fun getAll(): Flow<List<Product>> { return productDAO.getAll().map { list -> list.map { it.toDomain() } } } fun getProductById(id: Int): Flow<Product?> { // 改为可空Product return productDAO.getProductById(id).map { it?.toDomain() } // 安全调用toDomain() } }
2. ViewModel中处理null,控制Flow收集逻辑
先修正密封类的拼写错误(DetalheState应为DetailProductState),再新增数据不存在的状态,同时控制Flow的收集生命周期:
sealed class DetailProductState { data object UpdateSuccess : DetailProductState() data object DeleteSuccess : DetailProductState() data class GetByIdSuccess(val p: Product) : DetailProductState() data object DataNotFound : DetailProductState() // 新增数据不存在状态 data object ShowLoading : DetailProductState() } class ProductViewModel(private val repository: ProductRepository) : ViewModel(){ private val _stateList = MutableStateFlow<DetailProductState>(DetailProductState.ShowLoading) val stateList = _stateList.asStateFlow() // 保存收集Flow的Job,用于取消监听 private var productJob: Job? = null fun getProductById(id: Int) { // 先取消之前的收集,避免重复监听 productJob?.cancel() productJob = viewModelScope.launch { repository.getProductById(id).collect { result -> if (result != null) { _stateList.value = DetailProductState.GetByIdSuccess(result) } else { _stateList.value = DetailProductState.DataNotFound // 可选:数据不存在时停止收集 this.cancel() } } } } // ViewModel销毁时取消所有未完成的Job override fun onCleared() { super.onCleared() productJob?.cancel() } }
3. Fragment中新增状态处理
在Fragment的collect逻辑中,添加对DataNotFound状态的处理:
viewLifecycleOwner.lifecycleScope.launch { viewModel.stateList.collect { when (it) { DetailProductState.DeleteSuccess -> { Snackbar.make(binding.root, "Removed", Snackbar.LENGTH_SHORT).show() findNavController().popBackStack() } is DetailProductState.GetByIdSuccess -> { fillFields(it.p) } DetailProductState.DataNotFound -> { Snackbar.make(binding.root, "Product not found", Snackbar.LENGTH_SHORT).show() findNavController().popBackStack() } DetailProductState.ShowLoading -> {} DetailProductState.UpdateSuccess -> { Snackbar.make(binding.root, "Updated", Snackbar.LENGTH_SHORT).show() findNavController().popBackStack() } } } }
4. 可选:若无需实时监听,改用firstOrNull()
如果你只需要获取一次数据,不需要实时监听变化,可以直接用firstOrNull()终止Flow:
fun getProductById(id: Int) { viewModelScope.launch { val result = repository.getProductById(id).firstOrNull() _stateList.value = if (result != null) { DetailProductState.GetByIdSuccess(result) } else { DetailProductState.DataNotFound } } }
这种方式不会持续监听,数据删除后也不会触发后续事件,从根源避免问题。
内容的提问来源于stack exchange,提问作者user24618785
相关产品推荐
相关产品推荐

