Search code examples
androidandroid-jetpack-composeandroid-roomandroid-viewmodel

How to sync ViewModel State with (room) db?


I have been reading most android doc and more, but I can't find info about this topic. I know how to create and edit a state inside my viewModel, as described in many android code labs. But I'm not sure how to connect my state inside of the viewModel with my DB data.

About my app

  • The app should show a list of objects on the main screen
  • The app should store this list of objects inside my room db
  • The user should be able to add objects to this list

This is my viewModel:

@HiltViewModel
class MyViewModel @Inject constructor(
    private val myRepository: MyRepository
) : ViewModel() {

    private val _list = getList().toMutableStateList()
    val List: List<MyEntity>
        get() = _list

    private fun getList(): List<MyEntity> {
        return myRepository.getList()
    }

    fun addEntity(entity: MyEntity) {
        myRepository.upsertMyEntity(entity)
    }
}

Inside my Composable I initalize the viewModle like this:

myViewModel: MyViewModel = hiltViewModel<MyViewModel>()

and inside the composable I retrieve the value of the list like this:

myViewModel.List

The Problem

With the given code my ViewModel only gets the db state of the list once on startup

private val _list = getList().toMutableStateList()

How can the MutableStateList know that I added an object to the DB? Or vice versa, how can the DB know that I added an object to the List?

Possible solution

I could edit the "addEntity" function inside my viewModel to the following:

fun addEntity(entity: MyEntity) {
    myRepository.upsertMyEntity(entity)
    _list.add(entity)
}

But is this best practice? Shouldn't the MutableStateList be somehow connected to the db directly? This way, it feels like I'm adding something manually to the single source of truth (my database) and manually updating my state, which possibly could end up in a different state then my db. For example if the db throws an exception but the ui state gets changed anyways.


Solution

  • If you change your Dao to return a Flow<List<MyEntity>>, Room will create a flow that will automatically update its value when something changes in the database. Your view model just transforms that into a StateFlow:

    val list: StateFlow<List<MyEntity>> = myRepository.getList()
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = emptyList(),
        )
    

    Now, your composables can simply collect that flow into a Compose State like this:

    val list: List<MyEntity> by myViewModel.list.collectAsStateWithLifecycle()
    

    (needs dependency androidx.lifecycle:lifecycle-runtime-compose in gradle)

    This list in compose will now always contain the current state of your database: If you add a new value, this will automatically update too. addEntity can stay as it is, its sole responsibility yis to update the database, not the ui state.