Asynchronous Server Responses communication to view/viewmodel

from the CommonsWare Community archives

At 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.

  1. Is it safe to use Dispatchers.Main for a network call because I thought network calls could suspend the UI.
  2. 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.