Photo by Ian Froome on Unsplash
In the previous post, we reviewed the basic concepts in Kotlin coroutines. In this post, we will take a closer look at its implementation.
Suspend functions are the main component behind Kotlin coroutines, doing almost all the heavy-lifting when it comes to background work or concurrency. Without them, coroutines would not be able to work its magic.
What is a suspend function?
Standard functions can perform 2 basic operations:
- being invoked
- returning a value before finishing
Suspend functions, as their names states, are functions too. But we could say that they “expand” the standard function API, adding 2 extra capabilities:
- suspending execution
- resuming execution afterwards
As a result, suspend functions have the ability to stop the execution of a coroutine while they’re performing a task. When the task is done, suspend functions restart the coroutine execution too.
How do they work?
Let’s say we have an app where users can purchase items. In the final step of this process, we have to retrieve the selected item and, once we have it, then we use it to check for special offers.
A possible coroutine implementation would be similar to:
//XXX: start a coroutine...
launch(Dispatchers.MAIN) {
///XXX: call to some standard function...
startCheckout()
//XXX: in this block we change context and call several SUSPEND functions...
withContext(Dispatchers.IO) {
//XXX: 1st call to suspend function...
var purchasedItem = suspendFunctionThatReturnsItem(itemId)
//XXX: and another call to another suspend function....
var offers = anotherSuspendFunctionThatReturnsOffers(purchasedItem)
}
///XXX: call to some standard function...
finishCheckout()
}
In line 11, we are calling a suspend function to retrieve some value. At this point, coroutine execution would be stopped. Once the value is retrieved, coroutine execution is restarted. So it stores the value in the corresponding variable (left-side of the assignment) and keeps executing the next statements.
But in line 14 we find another call to a suspend function, so the whole “stop-restart” coroutine scenario is repeated. Once this suspend function is also done, coroutine execution will continue without interruptions, because there are no more calls to suspending functions in this block.
“Suspend functions have the ability to stop and restart the coroutine execution”
It’s important to keep in mind the fact that when a coroutine is stopped (or idle), the thread that it is running on is not blocked, so it can continue execution (performing some other tasks).
Declaration, invocation and execution of suspend functions
In Kotlin, suspend functions are declared using the suspend modifier, so the compiler knows that this piece of code can manage (by stopping and restarting) a coroutine execution.
suspend fun someSuspendFunction() {
//XXX: suspend function body goes here...
}
Suspend functions can only be used from a coroutine block (like the previous example) or inside another suspending function.
So we can call suspend functions from any coroutine launched in some scope: for instance, from the viewmodel scope available in Android viewmodels:
this.viewModelScope.launch {
startLoad()
//XXX: call to a suspend function from a coroutine....
var retrievedList = repo.retrieveList()
privateData.value = retrievedList?.map { PicView(it.id, it.urls.regular) }
endLoad()
}
“Suspend functions can only be called from coroutines or another suspend functions”
Suspend functions usually run on the dispatcher specified by the coroutine builder, unless we specify some other context using a “withContext()” block:
class PicsRepositoryImpl() {
override suspend fun retrieveList() : List<PicData> {
//XXX: execute in back thread
return withContext(Dispatchers.IO) {
api.fetchPics()
}
}
}
Coming up next…
We will wrap up this series writing about some additional concepts on coroutines. In the meantime, check out the following repo for a full sample app: