Kotlin is part of the new generation of languages that are gently gaining big traction against big names like Java and C++. Kotlin was designed and developed by JetBrains as an open source and statically typed programming language. Over the past few years, Kotlin has grown to become the main language of choice for Android developers, with Google advocating for it over Java nowadays.
The idea of coroutines is not new. It’s a concurrency pattern that allows you to achieve multiple processes at once. Coroutines can be found in JavaScript, Java, Clojure, C+, C++, PHP, Python, Ruby, and Swift, to name a few.
However, they may not be called a ‘coroutine’ directly, but the fundamentals of what a coroutine achieves is similar, if not exactly the same. Kotlin’s ideological implementation of coroutines borrows heavily on these languages that have an established experience in it.
But what is a coroutine?
A coroutine can be broken into two parts – co and routines. The co part represents the word cooperative and routines refers to functions.
Kotlin works in a serial fashion, meaning that a function cannot move forward if it relies on another function to give it some signal or data. The control flow shifts to the second function, giving it total power and the ability to hold an application hostage. You cannot negotiate the power away from the second function, making the process of running your app tightly coupled to the expected return.
With the introduction of coroutines, Kotlin is able to implement asynchronous code and achieve concurrency. This means that your Kotlin code has the ability to form layers, allowing for things to run in parallel with each other.
Cooperative functions become a possibility for Kotlin as coroutines allows the transfer of control via exit points, allowing for effective recursive loops to occur.
In part, it’s because coroutines are cheaper when it comes to memory than threads, making it more efficient on the machine it’s expected to run on.
For Kotlin, coroutines lets you write asynchronous and non-blocking code (that is, your code can continue to execute without having to stall and wait for things to complete).
In IntelliJ IDEA, start up a new Gradle project and follow through with the wizard set up. In your build.gradle
file, you need to make sure it’s configured for Kotlin 1.3 or higher. Coroutines isn’t supported in previous versions before Kotlin 1.3.
Your plugins settings should look something like this:
plugins { kotlin("multiplatform") version "1.4.0" }
It’s good to note that multiplatform can be replaced by other targets such as the jvm or js for JavaScript. For now, multiplatform will work for the purposes of this tutorial.
Coroutines is a library in Kotlin. This means that you’ll need to import it as a dependency. Here’s a sample code of what that looks like:
dependencies { ... implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9" }
Implementing this dependency will bring in kotlinx.coroutines
library. You also need to add it to Bintray JCenter
repository.
repositories { jcenter() }
Now you can start writing your coroutines inside src/main/kotlin
In a way, it’s like a light-weight thread. It can run in parallel, wait and communicate to each other. The easiest way is to think of them as functions that can be run in an asynchronous manner.
The major difference between coroutines and threads is to do with how memory is allocated. Coroutines simply has a cheaper memory allocation, therefore making more efficient by default.
Let’s pretend we have an orders list that we can fetch
using an API. So the function used to fetch the data can look something like this:
fun fetchOrders(){ val array = api.fetchOrdersList() updateUI(array) }
If you do this on the main thread, your app will certainly crash. Another way is to wrap this inside a thread. Then you start facing a life-cycle management issue. So we start using callbacks with subscriptions.
Next thing we know, we end up with code that looks like this:
fun onDescript(){ subscription1.cancel() subscription2.cancel() subscription3.cancel() subscription4.cancel() }
It’s a special kind of callback hell – long, unnecessary and somewhat tedious. Coroutines solves in Kotlin what async support did to JavaScript. It runs in the background as lightweight threads while allowing you to execute tasks relating to the background or UI changes.
To turn your function into a coroutine, use suspend
.
suspend fun fetchOrders(){ val array = api.fetchOrdersList() updateUI(array) }
And that’s it.
You might be asking yourself – surely, there has to be more to it than this. Indeed, you are correct.
You can also write a coroutine using withContext
, which will let you specify which thread you want to execute on. Kotlin has three types of dispatchers available and they are:
So writing your coroutine using withContext
looks something like this:
fun fetchOrders(){ withContext(Dispatchers.IO){ val array = api.fetchOrdersList() updateUI(array) } }
Alternatively, you can write a coroutine using launch
. What launch does is start a coroutine without blocking the current thread and returns a reference to the coroutine as a Job. Jobs can be cancelled if required to stop the execution of that particular coroutine.
Writing launch looks something like this:
launch { val array = api.fetchOrdersList() updateUI(array) }
By default, this will run on main and the context of the parent. You can specify dispatchers to determine which thread your launch coroutine runs on. This can look something like this:
launch(Dispatchers.IO){ val array = api.fetchOrdersList() updateUI(array) }
At some point, you might want to cancel the coroutine because you don’t need it anymore. It’s important that you cancel any coroutine you’re not using because it helps free up resources from unnecessary socket connections.
We have three flags that we can use to verify the status of a coroutine and they are:
isActive
– will return true if the function is still completing the states of a coroutineisCompleted
– is false by default and will flip to true when all the parent and child jobs are completedisCancelled
– this is also false by default unless an exception occurs or you cancel it. Only when one of these two things happen, it will flip to true.Canceling a coroutine looks something like this:
//hypothetical coroutine setup val scope = CoroutineScope(parentJob) val customerListJob = scope.launch{ ... } val orderListJob = scope.launch{ ... } //canceling coroutines in the scope scope.cancel()
That’s the basics of coroutines, what they are, how they work and how to write them.
If you’re already familiar with concepts like async and concurrency, writing coroutines should be a simple task to pick up. The alternative to achieving asynchronous in Kotlin is to use RxJava
– a library that allows for easier termination of subscriptions when activities end.
Terminating subscriptions is something we all learn early on to prevent memory leaks and wasted resources. This is one of RxJava
‘s jobs. The same idea can be applied to coroutines. While the scale and severity is not as high when it comes to coroutines, it’s still good practice to make sure that your coroutines are cancelled properly.
While the idea of coroutines is not exactly revolutionary or new, it’s implementation in a Kotlin context is. The ability to use coroutines in Kotlin means that the language has another tool up its curly braces for us to use. Any additional functionality and features that are based on time tested methodologies is a welcomed addition when it comes to Kotlin.