Retrofit With Kotlin Coroutines: What You Need To Know

In this ultimate guide, I will show you how to use Retrofit 2 With Kotlin Coroutines and what exactly you should know to use it.
This image is a featured image for Retrofit With Kotlinc Coroutines: What You Need To Know article and contains a Square Open Source logo in the foreground with a programmer using 3 monitors in the blurred background.

1. Introduction

Hello! In this article, I would like to show you how to use Retrofit 2 with Kotlin coroutines.

Please keep in mind, that this blog post is a continuation of the previous ultimate retrofit guide and we will focus on how to adjust the project to work properly with Kotlin coroutines. If you haven’t seen it yet, then you should definitely do that before heading to this one.

Either way, I can assure you that after finishing these two tutorials, you will have a full understanding of Retrofit and its features.

I’ve created this article as a response to Uduak comment- thank you for your feedback and I’m always happy to chat with you all 🙂

Finally, please keep in mind that the official support for coroutines has been added in Retrofit2 version 2.6.0.

2. Retrofit Kotlin Coroutines Imports

As the first step, we have to add coroutines functionality to our existing project.

To do so, we will use the kotlinx-coroutines-core. And to be on the same page, I will be using the version: 1.6.4.
 

Image shows two ebooks people can get for free after joining newsletter

 

2.1. Maven

If you are working with Maven, then you can add it to your project with the following lines:

<dependency>
  <groupId>org.jetbrains.kotlinx</groupId>
  <artifactId>kotlinx-coroutines-core</artifactId>
  <version>(VERSION)</version>
</dependency>

2.2. Gradle

Alternatively, we can use Gradle to fetch the library:

// build.gradle
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:<VERSION>'
// build.gradle.kts
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:<VERSION>")

3. Edit UserApi

Nextly, let’s navigate to the UserApi interface and make the necessary changes.

Please keep in mind, that the changes I’m gonna cover here can be applied to each function inside the UserApi (but to avoid boilerplate, I’ll focus on the GET function):

Note: as always, you can find the link to GitHub repository with all examples in the end of this article 🙂

interface UserApi {
  @GET("users")
  suspend fun getUsers(): Response<List<User>>
}

As we can see, only two adjustments are necessary here:

  • add suspend modifier- which in the coroutine’s context means that our function can be suspended (paused) and resumed (without blocking),
  • replace a Call with a Response – per the release notes, underneath it behaves just like fun getUsers(): Response<List<User>> invoked with Call.enqueue.

Of course, if we are not interested in headers and status codes, then we can simply return the List of users:

interface UserApi {
  @GET("users")
  suspend fun getUsers(): List<User>
}

4. Retrofit Client and Interceptors

When it comes to the Retrofit client with Kotlin coroutines- we can keep it as it is:

object RetrofitClient {

  // Base URL must end in /
  private const val BASE_URL = "http://localhost:8090/v1/"

  private val okHttpClient = OkHttpClient()
    .newBuilder()
    .addInterceptor(AuthorizationInterceptor)
    .addInterceptor(RequestInterceptor)
    .build()

  fun getClient(): Retrofit =
    Retrofit.Builder()
      .client(okHttpClient)
      .baseUrl(BASE_URL)
      .addConverterFactory(JacksonConverterFactory.create())
      .build()
}

object RequestInterceptor : Interceptor {
  override fun intercept(chain: Interceptor.Chain): Response {
    val request = chain.request()
    println("Outgoing request to ${request.url()}")
    return chain.proceed(request)
  }
}

object AuthorizationInterceptor : Interceptor {
  override fun intercept(chain: Interceptor.Chain): Response {
    val requestWithHeader = chain.request()
      .newBuilder()
      .header(
        "Authorization", UUID.randomUUID().toString()
      ).build()
    return chain.proceed(requestWithHeader)
  }
}

5. Retrofit User Service

On the other hand, we need to make small adjustments to the UserService class:

class UserService {

  private val retrofit = RetrofitClient.getClient()
  private val userApi = retrofit.create(UserApi::class.java)

  suspend fun successfulUsersResponse() {
    val usersResponse = userApi.getUsers()

    val successful = usersResponse.isSuccessful
    val httpStatusCode = usersResponse.code()
    val httpStatusMessage = usersResponse.message()

    val body: List<User>? = usersResponse.body()

  }

  suspend fun errorUsersResponse() {
    val usersResponse = userApi.getUsers()

    val errorBody: ResponseBody? = usersResponse.errorBody()

    val mapper = ObjectMapper()
    val mappedBody: ErrorResponse? = errorBody?.let { notNullErrorBody ->
      mapper.readValue(notNullErrorBody.string(), ErrorResponse::class.java)
    }
  }

  suspend fun headersUsersResponse() {
    val usersResponse = userApi.getUsers()

    val headers = usersResponse.headers()
    val customHeaderValue = headers["custom-header"]
  }
}

As we can see, we don’t have to invoke the execute() method anymore. In addition, we must mark functions with suspend– because the getUsers() can be either called only from a coroutine or another suspend function.

Finally, if we want to read the error body, we have to make use of the string() method, instead of toString(). As the name suggests, this one returns the response as a String. When we run the toString(), we will eventually end up with a runtime exception.

6. Test Retrofit With Kotlin Coroutines

As the last step, let’s test our Retrofit 2 functionality with Kotlin coroutines.

Although most probably, you will incorporate it into your Android project, to keep this blog post universal, I’ve prepared a simple main() function:

fun main() = runBlocking {
  val service = UserService()

  coroutineScope {
    launch(Dispatchers.IO) {
      delay(10_000)

      println("[${Thread.currentThread().name}] One")

      service.successfulUsersResponse()
    }
    launch(Dispatchers.IO) {
      println("[${Thread.currentThread().name}] Two")

      service.successfulUsersResponse()
    }
  }

  println("[${Thread.currentThread().name}] Done!")
}

If we run our program, we should see the following:

[DefaultDispatcher-worker-3] Two

# 10 seconds of delay

[DefaultDispatcher-worker-3] One

[main] Done!

As we can clearly see, everything is working as expected.

7. Retrofit With Kotlin Coroutines Summary

And that would be all for this blog post about Retrofit 2 with Kotlin coroutines.

As always, the source code for this article can be found in this GitHub repository.

Finally, if you enjoyed this content, or would like to ask me about anything, please let me know in the comments section below 🙂

Share this:

Related content

Newsletter
Image presents 3 ebooks with Java, Spring and Kotlin interview questions.

Never miss any important updates from the Kotlin world and get 3 ebooks!

You may opt out any time. Terms of Use and Privacy Policy