Kotlin Coroutines and Retrofit – A Practical Approach to Consuming REST APIs in Android
Tags: android, kotlin, coroutines, retrofit
Published: 2020-05-05 19:08:12

If you are a serious Android developer, consuming REST APIs at some point in your career is inevitable. This article will focus on how Retrofit can be bundled with Kotlin Coroutines to call asynchronous REST APIs, and we’ll build a small app to showcase a real-world example.

> The entire source code of this project is [available on GitHub](https://github.com/tatirajurishabh/Coroutines-Retrofit-Demo/blob/master/app/src/main/java/blog/rishabh/coroutinesretrofitdemo/ApiAdapter.kt).

## What will we build?
We'll be building a small app that retrieves a random dog image on demand. Our layout will contain an ImageView that fills up the top half of the screen, and a button which will call an API to get a random dog image whenever clicked.

We'll be using Retrofit to make the API calls, the [Dog CEO API](https://dog.ceo/dog-api/) to get random dog images, and we'll be using an image loading library called Coil to display the image.

## Prerequisites
Start off a new Android Studio project with a blank activity. Once the initial Gradle sync and indexing is completed, add the following lines in the dependencies section of `app` level `build.gradle`. Then re-sync your project to download all the dependencies.
```
// Retrofit and relevant converters
implementation 'com.squareup.retrofit2:retrofit:2.8.0'
implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
 
// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5'
 
// Coil for image loading
implementation 'io.coil-kt:coil:0.9.5'
```
You will also have to use Java 8, as it is required byboth Retrofit and Coil. Add these lines to your `app` level `build.gradle` file.
```
android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
}
```
## Setting up Retrofit
Our next step would be setting up the Retrofit client, through which we will be calling our APIs.

Firstly, let’s create an Interface called `ApiClient`. This will eventually become our Retrofit client. In this file, you’ll be defining all your API routes. We use the following scheme to define our APIs. 
```kotlin
@METHOD("/endpoint/of/the/api")
suspend fun ourMethodName(): Response<OurApiResponseModel>
```
Notice that the function is declared as suspend. Suspend functions are a core element of Coroutines. They’re essentially functions that could be paused or resumed during execution.

Here, the placeholders are:
- `METHOD`: The API method – GET, PUT, POST etc.
- `ourMethodName`: The function named relevant to the API call.
- `OurApiResponseModel`: A class which matches with the API’s JSON response.

In the Dog CEO API, sending a GET request to `https://dog.ceo/api/breeds/image/random` gives us a random dog image. The API responds with the JSON structure given below:
```javascript
{
    "message":"https:\/\/images.dog.ceo\/breeds\/hound-basset\/n02088238_3778.jpg",
    "status":"success"
}
```
We’ll now create a model class called `DogImageModel` which will be a replica of this response JSON. The keys become variables of the model class and the values will default to null. Retrofit will automatically handle the object mapping part for us. The `@Keep` annotation is to ensure the class is not obfuscated when using Proguard, which would cause a crash otherwise.
```kotlin
@Keep
class DogImageModel {
    var message: String? = null
    var status: String? = null
}
```
We’ll now define a new API inside the ApiClient file. Notice that I have omitted the domain name, the reason for which is explained in the next step.
```kotlin
@GET("/api/breeds/image/random")
suspend fun getRandomDogImage(): Response<DogImageModel>
```
At this point, our ApiClient file looks as follows.
```kotlin
interface ApiClient {
    @GET("/api/breeds/image/random")
    suspend fun getRandomDogImage(): Response<DogImageModel>
}
```
With our API defined and ready, we’ll create a new Kotlin Object called `ApiAdapter`. This file will hold the instance of the Retrofit client. Invoking the Retrofit builder, we pass in the base URL (the domain name we had omitted previously), an `OkHttpClient` instance as our HTTP client, and a `GsonConverterFactory` which will do the JSON-Object conversion for us.
```kotlin
object ApiAdapter {
    val apiClient: ApiClient = Retrofit.Builder()
        .baseUrl("https://dog.ceo")
        .client(OkHttpClient())
        .addConverterFactory(GsonConverterFactory.create())
        .build()
        .create(ApiClient::class.java)
}
```
We’re done with Retrofit setup. Let’s now head to the UI part.

## Building the UI
Our UI is pretty simple – a vertical LinearLayout with an `ImageView` and a `Button` as its children. Here’s the full XML code.

```xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
 
    <ImageView
        android:id="@+id/iv_dog_image"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>
 
    <Button
        android:id="@+id/btn_get_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="48dp"
        android:text="@string/get_random_dog_image"
        android:layout_gravity="center" />
 
</LinearLayout>
```

## Calling the API
With our UI and boilerplate code ready, we’ll now venture into Kotlin Coroutines. Simply put, Coroutines is an asynchronous utility in Kotlin that allows you to perform multithreading tasks easily. The usage is diverse and I recommend you to check out the [official guide](https://kotlinlang.org/docs/reference/coroutines-overview.html) to understand it further.

In order to call a Coroutine, we have to implement a `CoroutineScope`. This provides the scope in which the given coroutine should send out its results. In our case, we will implement the `MainScope()`, which creates the scope for UI components.

```kotlin
class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
 
        // Rest of the code goes here
    }
}
```
After we do that, we now have to call the API on a button click. We’ll set an `OnClickListener` to our Button. Our on-click action would be to call the API. Generally, using Coroutines, the API is called as follows:
- Wrap the entire call within a launch function call, passing the `Dispatchers.Main` dispatcher as a function parameter.
- Wrap the API call inside a try/catch block. This will allow us to handle any client errors (e.g. bad URL, no internet).
- Call the API by accessing the Retrofit client object in ApiAdapter, and then calling the appropriate API function.
- Check whether the API was successful or not, and then retrieve the response body.
- Do whatever you want to with the data, in our case get the image URL and display the image.

```kotlin
btn_get_image.setOnClickListener {
    launch(Dispatchers.Main) {
        // Try catch block to handle exceptions when calling the API.
        try {
            val response = ApiAdapter.apiClient.yourApiMethod()
            // Check if response was successful.
            if (response.isSuccessful && response.body() != null) {
                val data = response.body!!
                // Check for null safety.
                data.message?.let { imageUrl ->
                // Load URL into the ImageView using Coil.
                iv_dog_image.load(imageUrl)
            } else {
                // Show API error.
                Toast.makeText(
                    this@MainActivity,
                    "Error Occurred: ${response.message()}",
                    Toast.LENGTH_LONG).show()
            }
        } catch (e: Exception){
            // Show API error. This is the error raised by the client.
            Toast.makeText(this@MainActivity,
                "Error Occurred: ${e.message}",
                Toast.LENGTH_LONG).show()
        }
    }
}
```
And with that, our entire `MainActivity` looks like this
```kotlin
class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
 
        btn_get_image.setOnClickListener {
            // Use launch and pass Dispatchers.Main to tell that
            // the result of this Coroutine is expected on the main thread.
            launch(Dispatchers.Main) {
                // Try catch block to handle exceptions when calling the API.
                try {
                    val response = ApiAdapter.apiClient.getRandomDogImage()
                    // Check if response was successful.
                    if (response.isSuccessful && response.body() != null) {
                        val data = response.body()!!
                        // Check for null
                        data.message?.let { imageUrl ->
                            // Load URL into the ImageView using Coil.
                            iv_dog_image.load(imageUrl)
                        }
                    } else {
                        // Show API error.
                        Toast.makeText(
                                this@MainActivity,
                                "Error Occurred: ${response.message()}",
                                Toast.LENGTH_LONG).show()
                    }
                } catch (e: Exception) {
                    // Show API error. This is the error raised by the client.
                    Toast.makeText(this@MainActivity,
                            "Error Occurred: ${e.message}",
                            Toast.LENGTH_LONG).show()
                }
            }
        }
    }
}
```
That’s all. We’re ready to see our new creation in action! But before you run the code, make sure you add the internet permission at the top of your project’s `AndroidManifest.xml` file.

```xml
<uses-permission android:name="android.permission.INTERNET" />
```

If everything goes well, you’ll be able to see a random dog image every time you tap on the button. Congratulations!

![Random dog image](https://i.postimg.cc/JnP8DVWj/coroutine-retrofit-demo-random-dog.jpg)

## Closing Thoughts
Retrofit is an amazing HTTP client for Android. It reduces, if not eliminates, the time you spend doing mundane and time-consuming tasks like handling network calls and mapping your objects. The library, bundled with the new coroutines feature of Kotlin, makes consuming HTTP network calls pathetically simple!

As always, i'll be glad to answer your questions in the comments section. Until next time!