Handle Complex Network Call with Kotlin Coroutine + Retrofit 2
When you’ve been working with some Android applications, you may encounter the scenario where you have to make multiple network requests such as chained or parallel requests and combine their results.
To handle the mentioned scenario, some might say “let’s use RxJava!!!” but… come on the learning curve of RxJava is no joke, at least for me.
Don’t get me wrong, it doesn’t mean that I hate RxJava. RxJava is a really powerful library but Kotlin coroutine seem to suit my existing app architecture better.
In this article, I’ll demonstrate how to use coroutine in order to
- Chain multiple network request
- Create parallel network requests and process the responses when all of them are finished.
I won’t get into much detail about how coroutine work but I’ll focus on using it in action.
Introduction
Before we begin I would like to briefly address the concept and the commonly used functions in Coroutine.
Brief Concept
Writing the code asynchronously has alway been a pain for me, especially when dealing with complex network requests. It’s hard to write easy-to-read code and sometimes you may end up with a lot of callbacks in your code. Therefore, one of the purpose of Kotlin Coroutine is to help us write a neater code when working with asynchronous programming.
Here is what written in Kotlin Official document.
Coroutines simplify asynchronous programming by putting the complications into libraries. The logic of the program can be expressed sequentially in a coroutine, and the underlying library will figure out the asynchrony for us.
Launch and Async
launch
and async
are commonly used functions of coroutine.
Here are the official definition.
launch- Launches new coroutine without blocking current thread and returns a reference to the coroutine as a Job. The coroutine is cancelled when the resulting job is cancelled.
async- Creates new coroutine and returns its future result as an implementation of Deferred. The running coroutine is cancelled when the resulting object is cancelled.
Take a look at this piece of code as an example.
From the example, the difference between launch
and async
is that async
can return the future result which has a type of Deferred<T>
, and we can call await()
function to the Deferred
variable to get the result of the coroutine while launch
only execute the code in the block with out returning the result.
Let’s start!
Dependencies
Here is the things you need to add to your build.gradle
To make Retrofit work seamlessly with Kotlin Coroutine. I used the library called Kotlin Coroutine Adapter written by Jake Wharton.
API Interface
Lets create API interface
which define the api path we going to deal with.
Normally, when defining the path in API interface we’ll do something like this right ?
However, to make it work with coroutine, we may do it this way.
Basically, we made our API interface’s functions return the value as Deferred<T>
so that we can use them asynchronously later on.
Providing Retrofit API
In order to get the value from API interface’s functions as Deferred
, we need to specify Retrofit Call Adapter Factory this way addCallAdapterFactory(CoroutineCallAdapterFactory())
Chaining network requests
Let’s say we have to create two network requests in a row and we have to use the result from the first request to create the second one.
In our example, suppose the path of endpoint path3/{id}
depends on the result of the endpoint path1/
The code may look like this.
To break it down,
- First we call the
apiService.getObject1()
that returns result which has a type ofDeferred<Response<Object1>>
- As I have mention that we could call
await()
on theDeferred
variable to get the result, so we callawait()
on theapiService.getObject1()
so that whenever the result has arrived, it can be used to create another request. - After we get the result, this code
apiService.getObject3(it.id).await()
will be called - And the result will be handle by
successHandle
like thissuccessHandler(apiService.getObject3(it.id).await())
Create parallel network requests
Suppose we have to create two requests at the same time then process the results when the responses of both endpoints have returned. In our case, let’s call the endpoint /path1
and /path2
parallelly. The code going to look like this.
Let’s break it down.
- The requests happen at
val getObject1Task = apiService.getObject1()
andval getObject2Task = apiService.getObject2()
. Although the code is written in the sequential, the requests perform asynchronously. - Then we pass the results of both requests to be process in
successHandler
. The results are obtained bygetObject1Task.await().body()
andgetObject2Task.await().body()
- The
successHandler
will only be called only when bothgetObject1Task
andgetObject2Task
are finished.
Conclusion
The above example still doesn’t include the generic way to handle unsuccessful network requests. I may have to take sometime to find the appropriate way to deal that if I want to use the approach in the production app.
Overall, I’m impressed by how coroutine
help us simplify asynchronous programming. Moreover, It gets us out of the callback hell and make it easier to handle complex network requests. I think there is no excuse not to give coroutine
a try now.
If there is any mistake you spot in my article, please let me know. And please suggest on any point that you don’t agree. Thank you very much for reading.
Source
- https://medium.com/@andrea.bresolin/playing-with-kotlin-in-android-coroutines-and-how-to-get-rid-of-the-callback-hell-a96e817c108b
- https://proandroiddev.com/kotlin-coroutines-handling-concurrency-like-a-pro-retrofit2-coroutines-31cd0611fd91
- Kotlin for Android Developers book by Antonio Leiva(https://antonioleiva.com/kotlin-android-developers-book/)