How to update StateFlow inside the ViewModel

303 views Asked by At

I have this employee list in my composable:

val employeeViewModel: EmployeeViewModel = viewModel()
val employeeState by employeeViewModel.employeeState.collectAsState(ViewResult.Loading)

ALlEmployees(employeeState = employeeState,
      onRefresh = {
         // TODO: Re-fetch the list
      })

And this is my viewModel

var employeeState: StateFlow<ViewResult<List<Employee>>> = fetchEmployeesUseCase().stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = ViewResult.Loading,
        )
private set

I am really not sure how can I refetch the employee list. The flow needs to be re-trigerred somehow. Any help is appreciated.

1

There are 1 answers

0
Alvaro Ramirez Crisostomo On

Well, to achieve this let's start by refactoring the viewModel. The way I always handle it is this:

class EmployeeViewModel(
    private val repository: TestRepository
) : ViewModel() {

    // Here we store the value of the employee list status
    // We have the internal mutable state '_uiEmployeeState' and the public state 'uiEmployeeState'
    // The public state will be used in the composable to obtain the state value safely
    private val uiEmployeeState = MutableStateFlow<ViewResult<List<Employee>>>(ViewResult.Loading)
    val _uiEmployeeState: StateFlow<ViewResult<List<Employee>>> = uiEmployeeState

    // Use this function if you want the state to be updated every time the viewmodel is instantiated.
    init {
        fetchEmployees()
    }

    /**
     * Function that is responsible for updating the employee state
     *  - Use a 'viewModelScope' if the function is suspend, otherwise do it directly
     *  - Use 'update' to update the state value, you can also use '.value'
     *  - Use 'first' to get the first value of the flow and update the state
     */
    fun fetchEmployees() {
        uiEmployeeState.value = ViewResult.Loading
        viewModelScope.launch {
            uiEmployeeState.update {
                repository.fetchEmployeesUseCase().first()
            }
        }
    }

}

In the viewModel insider the fetchEmployees() you could set temporary the state as loading, that in case you have some animation or something like that, otherwise you can omit it and directly update the state. Also in my example we get the data from a repository, you can change it to your useCase

And on the part of the UI it would be like this:

val employeeViewModel: EmployeeViewModel = viewModel()
// You do not need to give it the initial value since the viewModel already has one
val employeeState by employeeViewModel._uiEmployeeState.collectAsState()

AllEmployees(
    employeeState = employeeState,
    onRefresh = { employeeViewModel.fetchEmployees() }
     // You can also use it as a lambda reference
    //onRefresh = employeeViewModel::fetchEmployees

)

Also as recommendation when you heard an state from the viewModel, you could check the library that is recommended:

-> collectAsStateWithLifecycle()

It only collects values from a Flow or StateFLow in a lifecycle-aware manner, allowing your app to save unneeded app resources. Specially for cases where you use StateFlow

Here I give you a link of reference: https://developer.android.com/jetpack/compose/state?hl=es-419#use-other-types-of-state-in-jetpack-compose