banner image 1 banner image 2

Ways to hit multiple API calls efficiently with Kotlin Coroutine threads?

April 12, 2023
4 mins
command
blog-img 1
Sobhana kr
Author

Here we can learn about different approaches and scenarios for parallel and concurrent API calls with kotlin Coroutines.

By Sobhana kr — “Optimistic Quoter


Let’s Consider a Situation, where call1 is expecting an id to make call2 with the fetched id from call1 response and based on the response from call2, call3 conditions, and params will be altered.

call 1 → call2 with id -> call3

How generally multiple dependant parallel calls are made.

Usually, the First call will be made and once we get the response, and manipulate the data then the next call will be made.

viewModelScope.launch {
val data1Response:BaseResponse<Data1>?
try{
val call1 = repository.getAPIcall1()
}
catch (ex: Exception) {
ex.printStackTrace()
}
processData(data1Response)
}

viewModel?.data1?.collect { dataResponse1 ->
repository.getAPIcall2()
}
viewModel?.data1?.collect { dataResponse2 ->
repository.getAPIcall3()
}

If we are fetching more than one API call. And each one is interdependent on other data. We need to sync up before data manipulation. Then Coroutines will be an optimized approach for us to asynchronously fetch data and wait for our expected outcome to do business logic.

To know about the basics of Kotlin Coroutines and its benefits refer https://medium.com/@krsobhana10/what-is-kotlin-coroutines-and-how-will-it-benefit-us-fb6072bbb313

To include Kotlin Coroutines in our project, First, we need to add the following dependencies in build.gradle file

dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")
}

To know about CoroutineScope and viewmodelScope refer https://medium.com/@krsobhana10/scopes-and-cancellation-of-coroutines-97a9a9333f8b which will be used in references.

Approach 1: Concurrent Approach with Wait Time

async-await with Kotlin Coroutines Concept

Async will be expecting a response from the call. Here call1 will provide data that should be synced with call2 and so on. The data manipulation can be done with the fetched response. But it will have some wait time between each call. With awaitAll(), we can combinedly call for a list of APIs at a time.

viewModelScope.launch {
val data1Response:BaseResponse<Data1>?
val data2Response: BaseResponse<Data2>?
val data3Response: BaseResponse<Data3>?
val call1 = async { repository.getAPIcall1()}
val call2 = async { repository.getAPIcall2()}
val call3 = async { repository.getAPIcall3() }
try {
data1Response = call1.await()
data2Response = call2.await()
data3Response = call3.await()
} catch (ex: Exception) {
ex.printStackTrace()
}
processData(data1Response, data2Response, data3Response)
}
suspend fun fetchData() =       
coroutineScope {
val mergedResponse = listOf(
async { getAPIcall1() },
async { getAPIcall2() }
)
mergedResponse.awaitAll()
}

Approach 2: Concurrent/ Parallel Approach with Thread Switching

withContext() will switch to seperate thread

It is similar to async-await. But it will have less overhead. Instead of withholding the main UI thread, withcontext will switch to a separate thread and perform the task. It will not have a wait time as async-await.

if runBlocking is added overlapping the withContext(), it will reverse the asynchronous and cancellable nature of Coroutines and blocks the thread. Until the particular task is done.

4 types of Dispatchers are there. IO, Main, Default, unconfined

  1. Dispatchers.Default uses a Main or IO thread based on resource availability.
  2. Dispatchers.Main blocks the main UI thread till the task gets executed
  3. Dispatchers.IO runs tasks in the background thread and will allow the main thread to be free.
  4. Dispatchers.Unconfined will be good when non-suspending API tasks with breaking and resuming scenarios take place.
viewModelScope.launch {
withContext(Dispatchers.Default) {
val apiResponse1 = api.getAPICall1()
val apiResponse2 = api.getAPICall2()
if (apiResponse1.isSuccessful() && apiResponse2.isSuccessful() { .. }
}
}

Approach 3: Parallel Approach with Data Merging

The third approach is slightly different and if we want two independent response and stitch it together to get a new response, then Zip Operator will help us to parallelly fetch the response.

Using Zip operator

repository.getData1()
.zip(repository.getData2()) { data1, data2 ->
return@zip data1 + data2
}
.flowOn(Dispatchers.IO)
.catch { e ->
..
}
.collect { it ->
handleSuccessResponse(..)
}

Out of these three approaches, when we want to call multiple parallel calls with wait time, async-await Approach will be suitable. When we want an even more efficient approach with thread switching, withcontext will be suitable. And to stitch two responses together and perform some data manipulation, Zip operator approach is suitable. Hope we learned about some Kotlin Coroutine multiple calls approaches and can follow some of them in our day-to-day use cases.


References :

[embed]https://medium.com/@krsobhana10/what-is-kotlin-coroutines-and-how-will-it-benefit-us-fb6072bbb313[/embed][embed]https://medium.com/@krsobhana10/what-is-kotlin-coroutines-and-how-will-it-benefit-us-fb6072bbb313[/embed]

Meet the team!!!

Author

Sobhana kr

Reviewed By

Android Fiend

Editor

Seema Jain


We at CaratLane are solving some of the most intriguing challenges to make our mark in the relatively uncharted omnichannel jewellery industry. If you are interested in tackling such obstacles, feel free to drop your updated resume/CV to careers@caratlane.com!
blog-img 2

Discussions

blog-img 3
5 mins
May 17, 2023
Sharing Data Between Controllers: Best Practices S...

This article will help you to understand the diffe

By Naveen C

blog-img 3
5 mins
March 21, 2023
Understanding Auto Layout and Constraints in Swift...

This article gives you an easy way of understandin

By Ramasamy P