
Since we started to develop the desktop, mobile or server-side applications, we often faced a problem in finding the solution to prevent our applications from blocking. We thought of avoiding the user’s wait time and the worst cause bottlenecks that would prevent an application from scaling. The word Asynchronous playing a vital role in modern programming, It can be used to increase the amount of work an app can perform in parallel and also allows us to run heavy tasks without UI freezing.
When it comes to Android development, there are many mechanisms to perform asynchronous tasks including:
- Threading
- Callbacks
- Futures, Promises et al.
- Reactive Extensions
- Coroutines
But it’s difficult to choose the most appropriate mechanism to implement because some have huge learning curve, while the others have tons of boilerplate code to implement and aren’t that concise. As of now, we are handling the multithreading by using Callbacks and blocking states because we don’t have any other simple way to do with thread-safe execution.
Coroutines is a very efficient way and complete framework to manage concurrency more efficiently and simply. Coroutines were added to Kotlin in version 1.3 and its based on established concepts from other languages. In this blog, we shall understand Coroutines in a simplified way.
What are Coroutines?
We can start our learning with an analysis of the term itself,
Coroutines = Co + Routines
Every one of us is familiar with ordinary routines, also called as subroutines or procedures. But in Java and Kotlin they are known as methods or functions. These routines are the basic building blocks of every codebase.
According to Wikipedia, a coroutine is a,
“sequence of program instructions, that performs a specific task, packaged as a Unit. This unit can then be used in programs wherever that particular task should be performed.”
Basically, Coroutines are lightweight threads, which is written over the top of the actual threading framework by taking advantage of cooperative nature to make it light and more powerful. It can be suspended and resumed in the mid of execution (i.e smart scheduling). Since the threads are managed by the OS, coroutines are managed by users as it can take advantage of the cooperation. Coroutines are available in many languages and Kotlin implemented with stack-less coroutines, which means they don’t have its own stack, so they are not mapping on the native thread.
Trust me, after trying out Kotlin Coroutines, you’ll realize they aren’t just another tool. They’re a whole new way of thinking about asynchronicity. Kotlin Coroutines help you to write asynchronous code more naturally. That is, in a sequential style of programming, which is more humanly-understandable and readable. Here we go for implementation with a simple example.
Before starting our implementation need to add these dependences in our Android project,
implementation “org.jetbrains.kotlinx:kotlinx-coroutines-core:x.x.x" implementation “org.jetbrains.kotlinx:kotlinx-coroutines-android:x.x.x”
We can take a common use-case of an Android application for below implementation,
- Fetch the list of fruits from server
- Show the fruits in the list UI
fun getFruits(): List<Fruit>) { // make network call // return fruit list } fun showFruits(fruits: List<Fruit>) { // show fruits in UI } fun getAndShowFruits() { val fruits = getFruits() showFruits(fruits) }
When we call getAndShowFruits function directly, it will throw the NetworkOnMainThreadException since the network is not allowed to perform on the main thread. To handle these cases, now we are using the callbacks. It will fetch the details in background thread and return the details in the main thread using callbacks.
fun getAndShowFruits() { getFruits { fruits -> showFruits(fruits) } }
For the same scenario we can use the coroutines as like this,
suspend fun getFruits(): List<Fruit> { return GlobalScope.async(Dispatchers.IO) { // make network call // return list of fruits }.await() } fun showFruits(fruits: List<Fruit>) { // show list of fruits } suspend fun getAndShowFruits() { val fruits = getFruits() // fetch on IO thread showFruits(fruits) // back on UI thread }
You see, many variables are looking new to us. Let’s have a look at what they are.
Dispatcher:
Dispatchers are used to help the coroutines in deciding the thread that the work has to be performed. It has three types majorly.
- Dispatchers.Default: It will perform CPU related works, such as sorting large lists, doing complex calculations and similar. A shared pool of threads on the JVM backs it.
- Dispatchers.IO: It will do networking or reading and writing from files. We can use it to perform any input and output works.
- Dispatchers.Main: It is the recommended dispatcher for performing UI-related events. For example, showing TextViews in an Activity, updating Views and so on.
Suspend:
Suspend is a function that could be started, paused and resumed. These functions are called from a coroutine or another suspend function only and it includes the keyword suspend. To use this function, we need to make our function as suspend too. If the suspending function has to suspend, it will simply pause its execution. By this way, you free up its current thread for other work. Once it’s done suspending, it will get the next free thread from the pool, to finish its work.
As we told already, the getAndShowFruits function can be called from another suspend function or a coroutine only. Since we couldn’t make the onCreate function as suspend we need to call it from the coroutines like below:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) GlobalScope.launch(Dispatchers.Main) { getAndShowFruits() } }
Which is same as,
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) GlobalScope.launch(Dispatchers.Main) { val fruits = getFruits() // fetch on IO thread showFruits(fruits) // back on UI thread } }
showFruits will run on UI thread because we have used the Dispatchers.Main to launch it.
There are two functions in Kotlin to start the coroutines which are as follows:
- launch{}
- async{}
Launch vs Async:
The basic and major difference is that launch{} does not return anything and the async{} will return the instance of Deferred<T>, which has an await() function that returns the result for coroutine.
We have a function getFruitsAndSaveInDatabase like below:
suspend fun getFruitsAndSaveInDatabase() { // fetch fruits from network // save fruits in database // and do not return anything }
Now, we can use the launch like below:
GlobalScope.launch(Dispatchers.Main) { getFruitsAndSaveInDatabase() // do on IO thread }
As the getFruitsAndSaveInDatabase does not return anything, we can use the launch to complete that task and then do something on Main Thread.
We can use runBlocking{} instead of launch. It will run new coroutine and blocks the current thread interruptibly until it’s completion. Due to main thread blocking, we can’t use this in production. One best use of this function is JUnit testing, where the test method will wait for the coroutine to complete the run.
runBlocking { delay(3000) }
Here you saw one more function delay(). It is the same as Thread.sleep() function to block the current thread. Since delay() is a suspending function, which results non-blocking suspension to allowing other Coroutines to execute. After the period of time delay (3000 milliseconds) finished, we will continue the execution of Coroutine from the point we left. It is equivalent to Thread.sleep(3000), since it is blocking call. The Coroutine is blocked for 3 seconds and only after the completion of the block, the other Coroutine will get the chance to run.
But when we need the result back to continue, we need to use the async. We have a function which will return the Fruit details with the given input of Android API levels like below:
suspend fun getFruitOneDetail(): Fruit { // make a network call // return fruit detail } suspend fun getFruitTwoDetail(): Fruit { // make a network call // return fruit detail }
Now, we can use the async like below:
GlobalScope.launch(Dispatchers.Main) { val fruitOne = async(Dispatchers.IO) { getFruitOneDetail() // get fruit one detail } val fruitTwo = async(Dispatchers.IO) { getFruitTwoDetail() // get fruit two detail } showDetails(fruitOne.await(), fruitTwo.await()) // back on UI thread }
Here, it makes both the network call in parallel, await for the results, and then calls the showDetails function.
Hope you have understood the difference between the launch function and the async function.
There is something called withContext.
suspend fun getFruits(): List<Fruit> { return GlobalScope.async(Dispatchers.IO) { // make network call // return list of fruits }.await() }
withContext is another way of writing the async function instead of writing await().
suspend fun getFruits(): List<Fruit> { return withContext(Dispatchers.IO) { // make network call // return list of fruits } }
But there are many more things that we should know about the withContext and the await. Now, let’s use withContext in our async example of getFruitOneDetail() and getFruitTwoDetail() in parallel.
GlobalScope.launch(Dispatchers.Main) { val fruitOne = withContext(Dispatchers.IO) { getFruitOneDetail() } val fruitTwo = withContext(Dispatchers.IO) { getFruitTwoDetail() } showDetails(fruitOne, fruitTwo) // back on UI thread }
When we use withContext, it will run in series instead of parallel. That is a major difference.
Scopes in Kotlin Coroutines:
While implementing Kotlin coroutine in Android, we need to cancel the background task as soon as the activity / fragment is destroyed. For this, we need proper Scopes. Here, we will learn how to use scopes to handle these types of situations. Assuming that our activity is the scope, the background task should get cancelled as soon as the activity is destroyed.
In the activity, we need to implement CoroutineScope.
class MainActivity : AppCompatActivity(), CoroutineScope { override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job private lateinit var job: Job }
In the onCreate and onDestroy function.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) job = Job() // create the Job } override fun onDestroy() { job.cancel() // cancel the Job super.onDestroy() }
Now, just use the launch like below:
launch { val fruitOne = async(Dispatchers.IO) { getFruitOneDetail() } val fruitTwo = async(Dispatchers.IO) { getFruitTwoDetail() } showDetails(fruitOne.await(), fruitTwo.await()) }
Once the activity is destroyed, the task also will get cancelled if it is running because we have defined the scope.
When we need the global scope which is our application scope, we can use the GlobalScope as below:
GlobalScope.launch(Dispatchers.Main) { val fruitOne = async(Dispatchers.IO) { getFruitOneDetail() } val fruitTwo = async(Dispatchers.IO) { getFruitTwoDetail() } }
So, even after the activity gets destroyed, the getFruitDetail functions will continue running as we have used the GlobalScope.
This is how the Scopes in Kotlin Coroutines are very useful.
Conclusion:
Now we have understood what exactly the Coroutines are. It does not replace threads, it’s more like a framework to manage threads. Like threads, coroutines also can run in parallel and wait for each other and then communicate. The biggest difference is that coroutines are very cheap or free so that we can create thousands of them, and pay very little in terms of performance. But the threads are expensive to start and keep around, where thousand threads can be serious challenge for a modern machine. And finally, you got to know how easy it is to switch between threads and return values asynchronously. We will see the exception handling in Coroutines in the upcoming blog.
– Sasikumar K,
Android Development Team,
Mallow Technologies.