Search code examples
androidkotlinmvvmandroid-roomkotlin-coroutines

How to properly use the result from a get query from a Room database


In my app I have a list of workouts, and at one point a want to get the name of one workout using its id in the Room database. For this, I made a SQL query in the DAO class to get the workout name using its id. my main problem is how to get it from the ViewModel to the view. The app uses coroutines.

DAO function:

@Query(
    "SELECT workout_name FROM workout_table " +
            "WHERE id = :workoutId"
)
suspend fun getWorkoutName(workoutId: Long): String

Repository function:

@WorkerThread
suspend fun getWorkoutName(workoutId: Long): String {
    return database.workoutDao().getWorkoutName(workoutId)
}

ViewModel function:

fun getWorkoutName(workoutId: Long): MutableLiveData<String> {
    val workoutName = MutableLiveData<String>()
    CoroutineScope(Dispatchers.IO).launch {
        workoutName.postValue(repository.getWorkoutName(workoutId))
    }
    return workoutName
}

View function (inside the fragment .kt class):

private fun setUpAppBar() {
    binding?.apply {
        viewModel.getWorkoutName(currentWorkoutId!!).observe(viewLifecycleOwner) {
            editWorkoutTopAppbar.title = it
        }
    }
}

Assume 'editWorkoutTopAppbar' is a textView.

This code works, but notice the ViewModel and the view. In the ViewModel I return a MutableLiveData variable and observe it in the view, I do this to use the variable when the value is ready. I realize I can get all the workouts at the beginning of the app and filter them to get the specific one, but my goal is to query the database.

What is a cleaner or more efficient way to get this value from the ViewModel?


Solution

  • In my projects with LiveData and ViewModel I utilise DataBinding for this. I set the ViewModel as a variable inside the xml and bind the variables of the ViewModel to the views:

    <layout>
    
        
        <data>
    
            <variable
                name="viewModel"
                type="path.ViewModel" />
        </data>
    
        <TextView
            android:text="@{viewModel.variableName}" />
    
    </layout>
    

    Fragment:

    val binding = DataBindingUtil.inflate(inflater, layoutRes, container, false)
    binding.setVariable(BR.viewModel, viewModel)
    

    Possible code improvement:

    You could also directly return a LiveData from your Dao:

    Dao:

    @Query(
        "SELECT workout_name FROM workout_table " +
                "WHERE id = :workoutId"
    ) 
    fun getWorkoutName(workoutId: Long): LiveData<String>
    

    Repo:

    fun getWorkoutName(workoutId: Long): LiveData<String> = 
      database.workoutDao().getWorkoutName(workoutId)
    

    ViewModel:

    // would always return only a LiveData instead of MutableLiveData so only the ViewModel can manipulate its value
    fun getWorkoutName(workoutId: Long): LiveData<String> =
            workoutName.postValue(repository.getWorkoutName(workoutId))
    

    On Coroutines:

    Inside a ViewModel you can also use viewModelScope for Coroutines.