Kotlin, Coroutine: Convert Async APIs into Sync
Convert Asynchronous APIs into Synchronous using Kotlin and Coroutine

Senior Android Engineer from Bangladesh. Love to contribute in Open-Source. Indie Music Producer.
Search for a command to run...
Convert Asynchronous APIs into Synchronous using Kotlin and Coroutine

Senior Android Engineer from Bangladesh. Love to contribute in Open-Source. Indie Music Producer.
No comments yet. Be the first to comment.
A practical guide to choosing the right coroutine primitive for your Android/Kotlin projects

This guide explains what Channels are, when to use them, how to use them correctly, and when NOT to use them β in simple, professional language. 1. What problem do Channels solve? In Kotlin coroutines, you often have multiple coroutines running at t...

Learn how to build preview-safe ViewModels in Jetpack Compose using Hilt.

HandlerInterceptor vs Filter in Spring Boot: A Practical Guide with JNDI Injection Prevention

Simple In-Memory Caching: A Tiny Trick with Massive Impact

If get to know something new by reading my articles, don't forget to endorse me on LinkedIn
Synchronous/asynchronous APIs are application programming interfaces that return data for requests either immediately or at a later time, respectively. Synchronous/asynchronous APIs provide a way to make immediate or scheduled requests for resources, data or services when available.
The synchronous and asynchronous nature of an API is a function of the time frame from the request to the return of data. In the case of synchronous APIs, the expectation is that there will be an immediate return of data. The application requests data and waits for it until a value is returned.
In the case of asynchronous APIs, the availability of a resource, service or data store may not be immediate. These APIs may provide a callback to the requester when the requested resource is ready. Asynchronous requests are useful in maintaining functionality in an application rather than tie up application resources waiting on a request.
An API may be synchronous where data or service availability, resources and connectivity are high and low latency is a requirement. An API may be asynchronous where data or service availability and connectivity are low or oversaturated with demand.
[Kotlin's Coroutine Knowledge is Required for further]
Kotlin has a lots of modern feature including one suspendCoroutine.
suspend inline fun <T> suspendCoroutine(
crossinline block: (Continuation<T>) -> Unit
): T
How it's work?
Obtains the current continuation instance inside suspend functions and suspends the currently running coroutine.
In this function both Continuation.resume and Continuation.resumeWithException can be used either synchronously in the same stack frame where the suspension function is run or asynchronously later in the same thread or from a different thread of execution. The subsequent invocation of any resume function will produce an IllegalStateException. [Official Doc]
class Worker {
/*Async API*/
fun doSomething(listener: Listener, throwError: Boolean) {
when (throwError) {
true -> {
Thread.sleep(3000)
listener.onError(Exception("Just a random exception..."))
}
else -> {
Thread.sleep(3000)
listener.onSuccess("Success")
}
}
}
interface Listener {
fun onSuccess(msg: String)
fun onError(e: Exception)
}
}
We have a class Worker, where we have a single API doSomething(listener : Listener, throwError : Boolean).
Let's take a look at the function signature, function takes a Worker.Listener (Callback) object as a parameter. Client get notified about the work result through the callback Worker.Listener.
But, it's an Asynchronous API and In certain cases, we might need to execute the API in a Synchronous way. So, how to do this?
We can solve this problem with the help of Kotlin's Coroutine.
suspend fun Worker.doSomethingSync(throwError: Boolean): String {
return suspendCoroutine {
doSomething(object : Worker.Listener {
override fun onSuccess(msg: String) {
it.resume("Success")
}
override fun onError(e: Exception) {
it.resumeWithException(e)
}
}, throwError)
}
}
Now, we have an Suspended Extension Function of Worker called doSomethingSync(). This API return a String object as a return type and throw Exception if occurs.
So, where is the magic happening?
doSomethingSync() return suspendCoroutine<String>. Inside the API, we are calling the Async API doSomething() and pass an Anonymous listener as the callback and when the callback returns events Success or Error, Continuation also return Success or Error. If the state is Error, doSomethingSync() throws Exception. When we call doSomethingSync() API, it blocks the current execution and waits for the result from the doSomethingSync() in a Sync way.
Example:
class Tester {
fun test() {
val worker = Worker()
/*Async Operation*/
/*Callback to get notified regarding the result*/
val listener = object : Worker.Listener {
override fun onSuccess(msg: String) {
println(msg)
}
override fun onError(e: Exception) {
e.printStackTrace()
println(e.message)
}
}
/*Do some work, don't throw exception*/
worker.doSomething(listener, false)
/*Do some work, throw exception*/
worker.doSomething(listener, true)
/*Async to Sync Operation*/
CoroutineScope(Dispatchers.Main).launch {
try {
/*Do some work, don't throw exception*/
println(worker.doSomethingSync(false))
/*Do some work, throw exception*/
println(worker.doSomethingSync(true))
} catch (e: Exception) {
e.printStackTrace()
println(e.message)
}
}
}
}