Search code examples
androidandroid-roomandroid-jetpack-compose

Update UI State in Realtime after Room Database Changes using Kotlin Flow


I have a Jetpack Compose Picture App where the user views Cached Photos from ROOM Database. The database has a isFavorite column which stores Booleans and is updated when the user clicks to like the photo.

This is my dao

@Query("SELECT * FROM astrophotoentity")
suspend fun getSavedAstroPhotos(): Flow<List<AstroPhotoEntity>>

@Query("UPDATE astrophotoentity SET isFavorite =:isFavorite WHERE id=:id")
suspend fun updateIsFavoriteStatus(id: String,isFavorite:Boolean)

This is my Repository code

override suspend fun updateIsFavoriteStatus(photo: AstroPhoto, isFavorite:Boolean) {
        dao.updateIsFavoriteStatus(photo.date,isFavorite)
    }

I am using a use case class for clean architecture.

class UpdateIsFavoriteStatusUseCase (private val repository: AstroRepository) {

suspend operator fun invoke(photo: AstroPhoto, isFavorite:Boolean){

    repository.updateIsFavoriteStatus(photo, isFavorite)
}

}

I also use a simple data class to hold state for the ViewModel.

data class PhotoState(
    val astroPhotos: List<AstroPhoto> = emptyList(),
    val isPhotosListLoading: Boolean = false,
    val errorMessage: String? = null)

This is the ViewModel which initializes the Photo State list to a list fetched from the DB. I use mutableStateOf() to hold the photo objects list. Here I update isFavorite column in the database depending on the passed event.

 @HiltViewModel
    class OverviewViewModel @Inject constructor(
        private val useCaseContainer: UseCaseContainer
    ) : ViewModel() {
    
        var state by mutableStateOf(PhotoState())
            private set
    
    init {

//set the state to the retrieved list of photo objects
... getPhotosListFromDb() 
}
    
     fun onEvent(event: OverviewEvent) { ....

     is OverviewEvent.OnMarkFavorite -> { ....
    
      useCaseContainer.updateIsFavoriteStatus(event.photo, event.isFavorite)}
    
      is OverviewEvent.OnRemoveFromFavorites -> { ....
            
    useCaseContainer.updateIsFavoriteStatus(event.photo, event.isFavorite)}

I pass the Photos list to the below composable.

@Composable
fun AstroPhotoComposable(
photo: AstroPhoto,
onMarkAsFavorite: () -> Unit,
onRemovePhoto: () -> Unit = {}
) {
//retrieve the isFavorite state using photo.isFavorite and cache it
  var isFavorite by remember{ mutableStateOf(photo.isFavorite) ... }

The issue I am facing is that when the photo changes from isFavorite/!isFavorite the state is not reflecting live on the UI.

I have read somewhere about cold and hot kotlin flows but I'm yet to wrap my head around this. I also tried to replace mutableStateOf with MutableStateFlow but the state is not updating on Database Changes.

Basically, I am looking to observe for database changes and get an up-to-date state similar to LiveData.

Any help is appreciated.


Solution

  • Have you ever tried updating the whole entity?

    @Update
    suspend fun update(myEntity: myEntity): Int
    

    And in my opinion it has to be without suspend

    @Query("SELECT * FROM astrophotoentity")
    fun getSavedAstroPhotos(): Flow<List<AstroPhotoEntity>>
    

    I'd do it like that.

    fun getSavedAstroPhotos() = dao.getSavedAstroPhotos().map{ it.toPhotoState() }
    

    And collect it in view model

     init {
       viewModelScope.launch { 
            getSavedAstroPhotos().collect{
                state = it
            }
        }
     }
    

    Perhabs it helps you link