Asynchronous Server Responses communication to view/viewmodel
from the CommonsWare Community archivesAt November 17, 2020, 5:42pm, Jan asked:
Using Okhttp and its callback onResponse method. Because http responses are asynchronous, how would one go about sending the response to the viewmodel and/or letting the view know a response has come in? Thanks.
At November 17, 2020, 6:21pm, mmurphy replied:
You could have your repository (or whatever is using OkHttp) return some reactive type that wraps the OkHttp callback, such as an RxJava Single
. Or, if you are using Kotlin and coroutines, use https://github.com/gildor/kotlin-coroutines-okhttp or the samples in Elements of Kotlin Coroutines for adapting OkHttp to a suspend
function. Or, there probably is an example of adapting an OkHttp callback to LiveData
.
From there, you would use whatever existing patterns exist in your app for having the view layer (activity/fragment) observe stuff from the viewmodel. For example, the OkHttp suspend
call could update a viewstate in a LiveData
or StateFlow
that the activity/fragment observes.
At November 18, 2020, 4:12am, Jan replied:
Thank you. After studying both of your links, I decided to go with Retrofit and use your example from Weather. I have 2 questions.
- Is it safe to use Dispatchers.Main for a network call because I thought network calls could suspend the UI.
- I’m getting the result body just fine. But how do I access the header. Your example doesn’t show processing the header of the response.
Here’s your code snippet that I am modeling. Thanks.
class MainMotor(application: Application) : AndroidViewModel(application) {
private val _results = MutableLiveData()
val results: LiveData = _results
fun load(office: String, gridX: Int, gridY: Int) {
val scenario = Scenario(office, gridX, gridY)
val current = _results.value
if (current !is MainViewState.Content || current.scenario != scenario) {
_results.value = MainViewState.Loading
viewModelScope.launch(Dispatchers.Main) {
val result = WeatherRepository.load(office, gridX, gridY)
_results.value = when (result) {
is WeatherResult.Content -> {
val rows = result.forecasts.map { forecast ->
val temp = getApplication<Application>()
.getString(
R.string.temp,
forecast.temperature,
forecast.temperatureUnit
)
RowState(forecast.name, temp, forecast.icon)
}
MainViewState.Content(scenario, rows)
}
is WeatherResult.Error -> MainViewState.Error(result.throwable)
}
}
}
}
}
At November 18, 2020, 1:24pm, Jan replied:
I found out that if I change the return type to Response, I can get to the headers. So that question is resolved.
At November 18, 2020, 1:46pm, mmurphy replied:
Retrofit uses its own background thread AFAIK. If I were doing network I/O on the main application thread, my sample would crash with a NetworkOnMainThreadException
.