2
votes

I'm using stateFlow in my viewModel to get the result of api calls with a sealed class like this :

sealed class Resource<out T> {
    data class Loading<T>(val data: T?): Resource<T>()
    data class Success<T>(val data: T?): Resource<T>()
    data class Error<T>(val error: Exception, val data: T?, val time: Long = System.currentTimeMillis()): Resource<Nothing>()
}
class VehicleViewModel @ViewModelInject constructor(application: Application, private val vehicleRepository: VehicleRepository): BaseViewModel(application) {

    val vehiclesResource: StateFlow<Resource<List<Vehicle>>> = vehicleRepository.getVehicles().shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1)
}

I would like to propose in my UI a button so that if the api call fails, the user can retry the call. I know there is a retry method on flows but it can not be called manually as it is only triggered when an exception occurs.

A common use case would be: The user have no internet connection, when the api call returns a network exception, I show a message to the user telling him to check its connection, then with a retry button (or by detecting that the device is now connected, whatever), I retry the flow.

But I can't figure a way to do it as you can not, like call flow.retry(). The actual retry method will be called as soon as an exception occurs. I don't want to retry immediately without asking the user to check it's connection, it wouldn't make sense.

Actually the only solution I found is to recreate the activity when the retry button is pressed so the flow will be reseted, but it's terrible for performances of course.

Without flows the solution is simple and there is plenties of examples , you just have to relaunch the job, but I can't find a way to do it properly with flows. I have logic in my repository between the local room database and the remote service and the flow api is really nice so I would like to use it for this use case too.

1

1 Answers

1
votes

I recommend keep the vehicleResource as a field in viewmodel and call a function to make an API call to fetch data.

private val _vehiclesResource = MutableStateFlow<Resource<List<Vehicle>>>(Resource.Loading(emptyList()))
val vehiclesResource: StateFlow<Resource<List<Vehicle>>> = _vehiclesResource.asStateFlow()

init {
    fetchVehicles()
}

private var vehicleJob : Job? = null

fun fetchVehicles() {
    vehicleJob?.cancel()

    vehicleJob = viewModelScope.launch {
        vehicleRepository.getVehicles().collect {
            _vehiclesResource.value = it
        }
    }
}

The API will be called in the constructor of viewmodel. And also you can call this function via the view (button's click listener) for error state.


P.S: I should mention that this line of your code has issue and SharedFlow couldn't be cast to StateFlow.

val vehiclesResource: StateFlow<Resource<List<Vehicle>>> = vehicleRepository.getVehicles().shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1)