I am studying Android development using the Architecture Components. I have some questions about the correct use of them. Here is my code, it is simple app that persists data locally.
ProductEntity
@Entity
data class ProductEntity (
@PrimaryKey(autoGenerate = true)
val id: Int,
val name:String,
)
ProductDAO
@Dao
interface ProductDAO {
@Insert
suspend fun insert(productEntity: ProductEntity)
@Query("SELECT * FROM productentity ORDER BY name")
fun getAll(): LiveData<List<ProductEntity>>
@Query("SELECT * FROM productEntity WHERE id=:id")
fun getProductById(id: Int): LiveData<ProductEntity>
}
ProductRepository
class ProductRepository (private val productDAO: ProductDAO) {
suspend fun insert(product: ProductEntity){
contatoDAO.insert(product)
}
fun getAll(): LiveData<List<ProductEntity>> {
return productDAO.getAll()
}
fun getProductById(id: Int): LiveData<ProductEntity>{
return productDAO.getProductById(id)
}
}
ProductViewModel
class ProductViewModel(private val repository: ProductRepository) : ViewModel(){
val allProducts:LiveData<List<ProductEntity>> = repository.getAll()
lateinit var product : LiveData<ProductEntity>
fun insert(productEntity: ProductEntity) = viewModelScope.launch(Dispatchers.IO){
repository.insert(productEntity)
}
fun getProductById(id: Int) {
viewModelScope.launch {
product = repository.getProductById(id)
}
}
companion object {
fun productViewModelFactory() : ViewModelProvider.Factory =
object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
): T {
val application = checkNotNull(
extras[APPLICATION_KEY]
)
return ProductViewModel(
(application as Application).repository
) as T
}
}
}
}
Finally, in my fragment:
class MyFragment : Fragment() {
lateinit var product: ProductEntity
val viewModel : ProductViewModel by viewModels { ProductViewModel.productViewModelFactory() }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.allProducts.observe(viewLifecycleOwner) { list ->
list?.let {
myAdapter.updateList(list)
}
}
val p = ProductEntity(1,'Smartphone')
viewmodel.insert(p)
viewModel.getProductById(1)
viewModel.product.observe(viewLifecycleOwner) { result ->
result?.let {
product = result
}
}
viewModel.delete(product)
}
}
QUESTIONS:
There is sense to use LiveData in an app that manage just local data? (see ProductDAO)
Assuming the answer to question 1 is "yes", even the fun getProductById(id: Int): LiveData should return a LiveData?
The Android site says that LiveData objects shouldn't be used in Repository (see HERE). However, I see in many examples Repository using LiveData, as my own code (ProductRepository)
Is better to use LiveData or Flow?
In my fragment, when I try to delete a product (retrieved via observer) using viewModel.delete(product), the app crashes. The error message says "NullPointerException: Parameter specified as non-null is null in ProductRepository.getProductById". Why this? I guess the observer remains and then the query to the DB with that ID returns null because that object no longer exists. But I don't know how to fix it. Any help?
Thanks!
1- Dont use LiveData in Dao. Only return your product. No need to more... Maybe flow; flows already uses coroutines. Support for asynchronous streams...
2-
3- LiveData is designed to update UI components based on their lifecycle. The repository, on the other hand, generally operates independently of UI components and typically has a longer lifespan. Storing LiveData in the repository can lead to lifecycle management issues.
4- Flow is better somewhere, livedata is better somewhere... Flow is little better from Livedata for me :)
5- waiting result ... You can fix it like this.
viewModel.product.observe(viewLifecycleOwner) { result ->
result?.let {
product = result
viewModel.delete(product)
}
}
observing taking a time. if you dont want crash, you will check product is null or not.
if(product != null) {
viewModel.delete(product)
}
OR
@PrimaryKey(autoGenerate = true)
val id: Int,
Id is autoGenerated ! First open of App id 1 is created and deleted. Another open of app; product taking new ids. 2, 3, 4...
val p = ProductEntity(1,'Smartphone')
1 is ineffective element.