Ui is not updating from viewmodel kotlin flow

Viewed 1106

I am quite new in Android Flow and JetPack compose,

I am trying to update my UI when mutable state is being changed , but this is not calling our composable , here is my code

    @Composable
    fun Grid() {
        val mainViewModel by viewModels<DashBoardViewModel>()
        mainViewModel.getData()
        when (val result = mainViewModel.mutableState.value) {
            is Resource.Success -> {
                LazyVerticalGrid(
                    cells = GridCells.Adaptive(100.dp)
                ) {
                    items(result.device.items.first().devices.count()) {
                        EachItem(it)
                    }
                }
            }
            is Resource.Error -> { Text(text = result.message) }
            Resource.Loading -> { CircularProgressIndicator() }
            Resource.Empty -> {}
            else -> { CircularProgressIndicator() }
        }
    }

ViewModel:

@HiltViewModel
class DashBoardViewModel @Inject constructor(
    private val dashBoardRepository: DashBoardRepository
) : ViewModel() {

   
    val mutableState = MutableLiveData<Resource>()

    fun getData() = viewModelScope.launch {

        flow {
            emit(Resource.Loading)
            try {
               val mResponse = dashBoardRepository.getDevice()
                emit(Resource.Success(mResponse))
            } catch (e: Exception) {
                e.printStackTrace()
                emit(Resource.Error("Error"))
            }
        }.flowOn(Dispatchers.IO).collect {
            mutableState.value = it
        }
    }
}
1 Answers

There are two problems in your code:

  • mainViewModel.mutableState.value gets only the current value from your mutable state. Your composable will not be notified when this value changes and thus it cannot reflect the change. If you want to use LiveData in your viewmodel, you have to use observeAsState() extension function which converts LivaData to State that can be automatically observed by composable function. Other option is to have (Mutable)State directly in your viewmodel. See this state explanation.
  • Your mainViewModel.getData() function will be called every time your Grid() function recomposes, which will be every time your mainViewModel.mutableState changes (once you observe it correctly). You definitely don't want that. Better solution would be to call getData() from your viewModel's init block, or, if you really need to call it from your composable function, use LaunchedEffect.

And, as a side note, the way you are creating flow and then collecting it into LiveData is really odd and unnecessary. You can do something like this instead:

fun getData() = viewModelScope.launch {
    mutableState.value = Resource.Loading
    try {
        val mResponse = dashBoardRepository.getDevice()
        mutableState.value = Resource.Success(mResponse)
    } catch (e: Exception) {
        e.printStackTrace()
        mutableState.value = Resource.Error("Error")
    }
}
Related