Refresh Data – Kotlin – Flows – MVVM – Jetpack Compose

Issue

I’m trying to implement a refresh button. I want to be able to trigger the api call again when the refresh button is clicked. Kind of confused on what the best practice is. Here is my view model and composable.

View Model:

@HiltViewModel
class CoinListViewModel @Inject constructor(
    private val getAllCoinsUseCase: GetListOfCoinsUseCase
): ViewModel() {

    private val _state = mutableStateOf(CoinsListState()) // not exposed because mutable
    val state: State<CoinsListState> = _state // expose this to composable because immutable

    init {
        getData()
    }

    // method to call the use case - put the data in the state object - then display state in the ui
    private fun getData(){

        getAllCoinsUseCase().onEach { resourceResult ->

            when(resourceResult){
                is Resource.Loading -> {
                    _state.value = CoinsListState(isLoading = true)
                }
                is Resource.Success -> {
                    _state.value = CoinsListState(coins = resourceResult.data ?: emptyList())
                }
                is Resource.Error -> {
                    _state.value = CoinsListState(
                        error = resourceResult.message ?: "Unexpected Error"
                    )
                }
            }
        }.launchIn(viewModelScope) // must launch in coroutine scope because using a flow
    }
}

Refresh Button:

@Composable
fun RefreshButton(navController: NavController, viewModel: CoinListViewModel) {
    // Refresh Button
    IconButton(
        onClick = {
            // Refresh Data
        },
        modifier = Modifier
            .semantics {
                contentDescription = "Refresh Button"
                testTag = "Refresh Button Test Tag"
            },

    ) {
        Icon(
            imageVector = Icons.Default.Refresh,
            contentDescription = "Refresh Icon"
        )
    }
}

Solution

Keep your getData function private and add another function you can call it onRefreshDataEvent for example, and on this function call getData. You may say why I can just call getData directly, but by this approach we are separating the refresh event from getData function because you can have another function called getCachedData and you call it instead or you can have a limit, for example you will not refresh data only one time per minute, so all of this logic will be on onRefreshDataEvent and your first getData function stay clean and do it’s job.

fun onRefreshDataEvent() {
    getData()
}

You can add a time check for example so the user couldn’t spam the refresh button, and the refresh could be used only a single time for each minute:

private var lastRefreshTime = 0
fun onRefreshDataEvent() {
    val currentTime = System.currentTimeMillis()
    if (currentTime - lastRefreshTime > (1000 * 60)) {
        getData()
        lastRefreshTime = currentTime
    }
}

So imagine that the last logic is implemented in getData function, the code will be messy.

Answered By – MoCoding

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published