Search code examples
kotlinandroid-roomviewmodelkotlin-flow

Accessing Room with Flow


I'm developing a simple Android app for educational purposes. The app manages product data locally using Room, Flow, Repository and ViewModel.

I'm collecting the flow in the ViewModel inside the method getProductById(id:Int).

The problem is: when a retrieve a product using getProductById(id:Int), everything is ok. But when I delete this product, an error occurs: *"java.lang.NullPointerException: Attempt to invoke virtual method on a null object reference" * I guess the flow is still collecting data about the product that was deleted. And, as it does not exists, this error occurs. How to fix it? It is possible to stop flow? Thanks

The ProductDAO uses Flow to get data:

@Dao
interface ProductDAO {
    @Insert
    suspend fun insert(productEntity: ProductEntity)


    @Query("SELECT * FROM productentity ORDER BY name")
    fun getAll(): Flow<List<ProductEntity>>

    @Query("SELECT * FROM productEntity WHERE id=:id")
     fun getProductById(id: Int): Flow<ProductEntity>


}

ProductRepository.

class ProductRepository (private val productDAO: ProductDAO) {

    suspend fun insert(product: ProductEntity){
        contatoDAO.insert(product)
    }

    fun getAll(): Flow<List<Product>> {
        return productDAO.getAll().map{list->list.map{it.toDoamin()}}
    }

    fun getProductById(id: Int): Flow<Product>{
       return productDAO.getProductById(id).map{it.toDoamin()}

    }
}

In my ViewModel I collect the Flow int the getProductById() method


sealed class DetailProductState {
    data object UpdateSuccess : DetalheState()
    data object DeleteSuccess : DetalheState()
    data class GetByIdSuccess(val p: Product) : DetailProductState()
    data object ShowLoading : DetalheState()

}


 class ProductViewModel(private val repository: ProductRepository) : ViewModel(){

    
     private val _stateList = MutableStateFlow<DetailProductState>(DetailProductState.ShowLoading)
     val stateList = _stateList.asStateFlow()

     
     fun getProductById(id: Int) {
         viewModelScope.launch {
             repository.getProductById(id).collect{result->

                 _stateList.value=  DetailProductState.GetByIdSuccess(result)

             }

         }
}

Finally, in my fragment

 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.ShowLoading -> {}
                    DetailProductState.UpdateSuccess -> {
                        Snackbar.make(
                            binding.root,
                            "Updated",
                            Snackbar.LENGTH_SHORT
                        ).show()
                        findNavController().popBackStack()
                    }
                }
            }
        }

I am very grateful in advance for any help


Solution

  • The flow emits every time there’s a change to the database, and emits null when the query has no result. So define it as return type Flow<ProductEntity?> to prevent it from throwing a NullPointerException. You can choose how you want to respond to null values in your collector, or just ignore them by calling filterNotNull() on the flow before collecting.