androidandroid-roomandroid-livedataandroid-viewmodel

Android development - Room, LiveData, Repository and ViewModel


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:

  1. There is sense to use LiveData in an app that manage just local data? (see ProductDAO)

  2. Assuming the answer to question 1 is "yes", even the fun getProductById(id: Int): LiveData should return a LiveData?

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

  4. Is better to use LiveData or Flow?

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


Solution

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