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.
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 withCall.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 🙂