Uncovering Kotlin Coroutines (on Android)
Let's suspend and find out what coroutines are. Here's a good explanation I found on Kotlin's hands-on website .
Coroutines are computations that run on top of threads and can be suspended. By saying "suspended", we mean that the corresponding computation can be paused, removed from the thread, and stored in memory. Meanwhile, the thread is free to be occupied with other activities. When the computation is ready to be continued, it gets returned to a thread (but not necessarily to the same one).
I tried wrapping my head how this 'suspension' really happens e.g. who does the work when the coroutine is suspended? So I went to the #coroutines channel on Kotlin's Slack group, and popped the question . I got informative feedback which sparked a nice conversation. Check it out.
Side note: I wrote an ADB project with coroutines version 0.22.5 (update long overdue 😱) in which I explored the power of coroutine channels since the ADB client has to interact back and forth with the ADB daemon running on the device.
Apart from the
suspend (modifier) keyword, which you mostly work with, this library has other concepts worth exploring to enable us employ it properly. Let's check them out. (A deep dive talk on the inner workings of coroutines can be found on this talk).
This is an interface that defines the scope of any coroutine you start. It's the main backbone for Structured Concurrency in coroutines. Classes with a well-defined lifecycle such as
Activitieson Android expose extension properties of type CoroutineScope to allow stopping execution of any coroutines started within their scope when their lifecycle ends.
This is a kind of a set housing the state of affairs of the coroutine. It contains elements that are singleton instances of itself such as the
Dispatcher. Every CoroutineScope implementation has a CoroutineContext associated with it.
This is what determines the thread or thread pool in which to run your coroutine (can also let the coroutine run unconfined though usage is discouraged). Some standard dispatcher implementations are: IO, Default, Unconfined, Main.
This is an instance of CoroutineContext (see above) that represents any coroutine we start. It allows parent-child relationships in which a new coroutine becomes a child of the parent coroutine's job in support of Structured Concurrency. This allows us to cancel a parent job, in turn cancelling all children that inherit from it. It also permits exception propagation from children coroutines up to parents (this is configurable).
I hope the above definitions give you some understanding of coroutines and their support framework. It's by no means an authoritative guide to coroutines and you should refer to their official site.
Say we want to make a network call using Retrofit to get some status from our backend. Invoking such a call on the main thread gives us the all well known NetworkOnMainThreadException. Thus this job needs to be done on a 'background' thread -- a thread other than the main one. We could launch a JVM thread, use Handlers, Executor Service etc. All these need to be babysitted in regards to lifecycle and thus not the best choice. Coroutines to the rescue! The folks at Android have done the babysitting and orchestrated the lifecycle dance for us in the AndroidX artifacts. All we need to do is take advantage of that.
We'll create a suspending function to invoke the network call (Retrofit added support for suspending functions as from version 2.6.0). Then launch the coroutine within a viewModelScope. And voilà! We've got a network call on it's way. When the ViewModel goes away, the call is gracefully cancelled -- it's even nicer that Retrofit inherently cooperates with this!
Coroutines provide us with a wonderful api for designating and executing async work. With one keyword to learn
suspend, it's easy to pick up and use. I like to think of the suspend keyword as a way of saying:
Hey Dispatcher! Work on this and let me know when you're done.
Therefore, launch coroutines on your choice builder to do background, perhaps concurrent work, giving them the correct dispatcher to run on, confined within the right scope and you have a powerful tool to build responsive Android apps. Furthermore, no fear. Coroutine are lighter than threads so be daring!