Over the years, Android developers have been doing networking with the use of some APIs from the Android SDK and other tools which always involved a lot of work. The use of Retrofit became the standard when we are talking about consuming APIs. This means having an additional layer on top of Java and the logic of our app.

With the introduction of Kotlin a few years ago and with their native support for asynchronous operations with Coroutines, developers were provided with tools easier to use. Retrofit is still needed though, but getting out most of the language feels like if we are not using it.

In this post, I’m going to show you how to use Kotlin, Coroutines, Retrofit and, LiveData to manage networking on an Android app.

Setup

We’ll need to add the correspondent dependencies for Kotlin and Coroutines support, Retrofit and LiveData, into our app. Take a look at the links at the end of this post for documentation.

Define API and configure client

The next step is to define the API our app will be consuming. This is done with Retrofit classes.

interface DataService {
    @retrofit2.http.GET("/v1/data")
    suspend fun retrieveData(): retrofit2.Response<Data>
}

The next thing that’s done here is to initialize the service using Retrofit built-in adapter. This allows us to set up a host, a custom client using OkHttp, JSON deserializers, and many other configurations you can find on the official docs.

private val service: DataService = retrofit2.Retrofit.Builder().apply {
    baseUrl("https://dummyapi.wawand.co")
    client(okhttp3.OkHttpClient.Builder().build())
}.build().run {
    create(DataService::class.java)
}

Once the service is set up, it’s time to create the functions that are going to be called from our ViewModel or directly from the UI (not recommended).

// DataAPI.kt
suspend fun retrieveData(): retrofit2.Response<Data> = 
        service.retrieveData()

On the ViewModel, we’ll be using LiveData to launch the Coroutine that handles the request and dispatch the response to the UI once it is completed.

// MyViewModel.kt
fun retrieveData(context: Context): 
    LiveData<retrofit2.Response<Data>> = liveData(Dispatchers.IO) {
        emit(DataAPI(context).retrieveData())
    }

Finally on the UI, just need to use our view model to make the request, observe the results and handle the response according to our app logic demands.

// MyActivity.kt
viewModel.retrieveData().observe(this) { response ->
    if (response.isSuccessful) {
        refreshUI(response.body)
        return
    }
    showError(response.message())
}

And with this implementation with Kotlin, Coroutines, Retrofit, OkHttp and ViewModel with LiveData we can consume an API with the use of modern technologies.

Bonus

To make easier the management of our response, a very common practice is to create a wrapper class to expose to the API consumers, instead of exposing the Retrofit’s Response class directly.

// MyResponse.kt

sealed class MyResponse<out T>

class Success<out T>(val data: T): MyResponse<T>

class Failure<out T>(val errorMessage: String): MyResponse<T>

Our function on the API class will look like this:

// DataAPI.kt
suspend fun retrieveData(): MyResponse<List<Users>> = 
    service.retrieveData().let { response ->
        // Apply any business logic needed here before returning the response
        return if (response.isSuccessful) {
            Success(response.body) 
        } else Failure(response.message())
    }

On our ViewModel class we will have a signature change for the retrieveData method:

// MyViewModel.kt
fun retrieveData(context: Context): 
    LiveData<MyResponse<Data>> = liveData(Dispatchers.IO) {
        emit(DataAPI(context).retrieveData())
    }

And our Activity will look like this:

// MainActivity.kt
viewModel.retrieveData().observe(this) { response ->
    when (response) {
        is Success -> refreshUI(response.data)
        is Failure -> showError(response.errorMessage)
    }
}

And with this more idiomatic implementation, we’re able to take advantage of some Kotlin features, like sealed classes, generics, typed conditionals, and smart casts. It’s up to you to use most of the language as you can but I strongly recommend doing it.

Further Reading

This is a very basic example of how to use this set of tools to do networking on Android. If you liked this post and want to take a look at each one of the tools and technologies that I used here, documentation for them is really good and is widely backed up by the Android community.