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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 06:17:09