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
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.