Handle Complex Network Call with Kotlin Coroutine + Retrofit 2

Boonya Kitpitak
The OOZOU Blog
Published in
4 min readJul 26, 2018
https://www.pexels.com/photo/abstract-art-background-brown-279844/

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 of Deferred<Response<Object1>>
  • As I have mention that we could call await() on the Deferred variable to get the result, so we call await() on the apiService.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 this successHandler(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() and val 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 by getObject1Task.await().body() and getObject2Task.await().body()
  • The successHandler will only be called only when both getObject1Task and getObject2Task 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

--

--

Boonya Kitpitak
The OOZOU Blog

Android Developer at Oozou. Also Guitarist and Headbanger