0% found this document useful (0 votes)
16 views

Coroutines 2

Uploaded by

Alex Trujillo
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
16 views

Coroutines 2

Uploaded by

Alex Trujillo
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 10

Introduction 6

Sean McQuillan - a Developer Advocate at


Google. With a decade of experience at Twilio
and other San Francisco startups, he is an ex-
pert at building apps that scale. Sean is pas-
sionate about using great tooling to build high-
quality apps quickly. When he is not working
on Android, you can find him fiddling on the
piano or crocheting hats.

Igor Wojda - a passionate engineer with over


a decade of software development experience.
He is deeply interested in Android application
architecture and the Kotlin language, and he
is an active member of the open-source com-
munity. Igor is a conference speaker, technical
proofreader for the ‘Kotlin In Action’ book,
and author of the ‘Android Development with Kotlin’ book. Igor
enjoys sharing his passion for coding with other developers.
Jana Jarolimova - an Android developer at Avast. She started her ca-
reer teaching Java classes at Prague City University, before moving
on to mobile development, which inevitably led to Kotlin and her
love thereof.
Richard Schielek - an experienced developer and an early adopter of
Kotlin and coroutines, using both in production before they became
stable. Worked in the European space industry for several years.
Vsevolod Tolstopyatov - a team lead of the Kotlin Libraries team. He
works at JetBrains and is interested in API design, concurrency, JVM
internals, performance tuning and methodologies.
Ibrahim Yilmaz, Dean Djermanović and Dan O’Neill.
I would also like to thank Michael Timberlake, our language re-
viewer, for his excellent corrections to the whole book.
Part 1: Understanding Kotlin
Coroutines
Before we start our adventure with the Kotlin Coroutines library,
let’s start with some more basic concepts. What are coroutines? How
does suspension work? What does it all look like under the hood? We
will explore all this while learning some useful tools and practices.

7
Why Kotlin Coroutines? 8

Why Kotlin Coroutines?

Why do we need to learn Kotlin Coroutines? We already have well-


established JVM libraries like RxJava or Reactor. Moreover, Java
itself has support for multithreading, while many people also choose
to just use plain old callbacks instead. Clearly, we already have many
options for performing asynchronous operations.
Kotlin Coroutines offer much more than that. They are an imple-
mentation of a concept that was first described in 1963² but waited
years for a proper industry-ready implementation³. Kotlin Corou-
tines connects powerful capabilities presented by half-century-old
papers to a library that is designed to perfectly help in real-life use
cases. What is more, Kotlin Coroutines are multiplatform, which
means they can be used across all Kotlin platforms (like JVM, JS,
iOS, and also in the common modules). Finally, they do not change
the code structure drastically. We can use most Kotlin coroutines’
capabilities nearly effortlessly (which we cannot say about RxJava or
callbacks). This makes them beginner-friendly⁴.
Let’s see it in practice. We will explore how different common use
cases are solved by coroutines and other well-known approaches. I
will show two typical use cases: Android and backend business logic
implementation. Let’s start with the first one.

²Conway, Melvin E. (July 1963). “Design of a Separable Transition-


diagram Compiler”. Communications of the ACM. ACM. 6 (7): 396–
408. doi:10.1145/366663.366704. ISSN 0001-0782. S2CID 10559786
³I believe that the first industry-ready and universal coroutines
were introduced by Go in 2009. However, it is worth mentioning
that coroutines were also implemented in some older languages, like
Lisp, but they didn’t become popular. I believe this is because their
implementation wasn’t designed to support real-life cases. Lisp (just
like Haskell) was mostly treated as a playground for scientists rather
than as a language for professionals.
⁴This does not change the fact that we should understand corou-
tines to use them well.
Why Kotlin Coroutines? 9

Coroutines on Android (and other frontend


platforms)

When you implement application logic on the frontend, what you


most often need to do is:

1. get some data from one or many sources (API, view element,
database, preferences, another application);
2. process this data;
3. do something with this data (display it in the view, store it in a
database, send it to an API).

To make our discussion more practical, let’s first assume we are


developing an Android application. We will start with a situation in
which we need to get news from an API, sort it, and display it on the
screen. This is a direct representation of what we want our function
to do:

fun onCreate() {
val news = getNewsFromApi()
val sortedNews = news
.sortedByDescending { it.publishedAt }
view.showNews(sortedNews)
}

Sadly, this cannot be done so easily. On Android, each application


has only one thread that can modify the view. This thread is very im-
portant and should never be blocked. That is why the above function
cannot be implemented in this way. If it were started on the main
thread, getNewsFromApi would block it, and our application would
crash. If we started it on another thread, our application would crash
when we call showNews because it needs to run on the main thread.

Thread switching

We could solve these problems by switching threads. First to a thread


that can be blocked, and then to the main thread.
Why Kotlin Coroutines? 10

fun onCreate() {
thread {
val news = getNewsFromApi()
val sortedNews = news
.sortedByDescending { it.publishedAt }
runOnUiThread {
view.showNews(sortedNews)
}
}
}

Such thread switching can still be found in some applications, but it


is known for being problematic for several reasons:

• There is no mechanism here to cancel these threads, so we


often face memory leaks.
• Making so many threads is costly.
• Frequently switching threads is confusing and hard to man-
age.
• The code will unnecessarily get bigger and more complicated.

To see those problems well, imagine the following situation: You


open and quickly close a view. While opening, you might have
started multiple threads that fetch and process data. Without
cancelling them, they will still be doing their job and trying to
modify a view that does not exist anymore. This means unnecessary
work for your device, possible exceptions in the background, and
who knows what other unexpected results.
Considering all these problems, let’s look for a better solution.

Callbacks

Callbacks are another pattern that might be used to solve our prob-
lems. The idea is that we make our functions non-blocking, but we
pass to them a function that should be executed once the process
started by the callback function has finished. This is how our func-
tion might look if we use this pattern:
Why Kotlin Coroutines? 11

fun onCreate() {
getNewsFromApi { news ->
val sortedNews = news
.sortedByDescending { it.publishedAt }
view.showNews(sortedNews)
}
}

Notice that this implementation does not support cancellation. We


might make cancellable callback functions, but it is not easy. Not
only does each callback function need to be specially implemented
for cancellation, but to cancel them we need to collect all the objects
separately.

fun onCreate() {
startedCallbacks += getNewsFromApi { news ->
val sortedNews = news
.sortedByDescending { it.publishedAt }
view.showNews(sortedNews)
}
}

Callback architecture solves this simple problem, but it has many


downsides. To explore them, let’s discuss a more complex case in
which we need to get data from three endpoints:

fun showNews() {
getConfigFromApi { config ->
getNewsFromApi(config) { news ->
getUserFromApi { user ->
view.showNews(user, news)
}
}
}
}

This code is far from perfect for several reasons:

• Getting news and user data might be parallelized, but our


current callback architecture doesn’t support that (it would be
hard to achieve this with callbacks).
Why Kotlin Coroutines? 12

• As mentioned before, supporting cancellation would require a


lot of additional effort.
• The increasing number of indentations make this code hard to
read (code with multiple callbacks is often considered highly
unreadable). Such a situation is called “callback hell”, which
can be found especially in some older Node.JS projects:

• When we use callbacks, it is hard to control what happens after


what. The following way of showing a progress indicator will
not work:

fun onCreate() {
showProgressBar()
showNews()
hideProgressBar() // Wrong
}

The progress bar will be hidden just after starting the process of
showing news, so practically immediately after it has been shown.
To make this work, we would need to make showNews a callback
function as well.
Why Kotlin Coroutines? 13

fun onCreate() {
showProgressBar()
showNews {
hideProgressBar()
}
}

That’s why the callback architecture is far from perfect for non-
trivial cases. Let’s take a look at another approach: RxJava and other
reactive streams.

RxJava and other reactive streams

An alternative approach that is popular in Java (both in Android and


backend) is using reactive streams (or Reactive Extensions): RxJava
or its successor Reactor. With this approach, all operations happen
inside a stream of data that can be started, processed, and observed.
These streams support thread-switching and concurrent processing,
so they are often used to parallelize processing in applications.
This is how we might solve our problem using RxJava:

fun onCreate() {
disposables += getNewsFromApi()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map { news ->
news.sortedByDescending { it.publishedAt }
}
.subscribe { sortedNews ->
view.showNews(sortedNews)
}
}

The disposables in the above example are needed to


cancel this stream if (for example) the user exits the
screen.

This is definitely a better solution than callbacks: no memory leaks,


cancellation is supported, proper use of threads. The only problem
is that it is complicated. If you compare it with the “ideal” code from
the beginning (also shown below), you’ll see that they have very little
in common.
Why Kotlin Coroutines? 14

fun onCreate() {
val news = getNewsFromApi()
val sortedNews = news
.sortedByDescending { it.publishedAt }
view.showNews(sortedNews)
}

All these functions, like subscribeOn, observeOn, map, or subscribe,


need to be learned. Cancelling needs to be explicit. Functions need
to return objects wrapped inside Observable or Single classes. In
practice, when we introduce RxJava, we need to reorganize our code
a lot.

fun getNewsFromApi(): Single<List<News>>

Think of the second problem, for which we need to call three end-
points before showing the data. This can be solved properly with
RxJava, but it is even more complicated.

fun showNews() {
disposables += Observable.zip(
getConfigFromApi().flatMap { getNewsFromApi(it) },
getUserFromApi(),
Function2 { news: List<News>, config: Config ->
Pair(news, config)
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { (news, config) ->
view.showNews(news, config)
}
}

This code is truly concurrent and has no memory leaks, but we need
to introduce RxJava functions such as zip and flatMap, pack a value
into Pair, and destructure it. This is a correct implementation, but
it’s quite complicated. So finally, let’s see what coroutines offer us.
Why Kotlin Coroutines? 15

Using Kotlin coroutines

The core functionality that Kotlin coroutines introduce is the ability


to suspend a coroutine at some point and resume it in the future.
Thanks to that, we might run our code on the Main thread and
suspend it when we request data from an API. When a coroutine is
suspended, the thread is not blocked and is free to go, therefore it
can be used to change the view or process other coroutines. Once
the data is ready, the coroutine waits for the Main thread (this is a
rare situation, but there might be a queue of coroutines waiting for
it); once it gets the thread, it can continue from the point where it
stopped.

This picture shows theupdateNews and updateProfile functions running on the Main
thread in separate coroutines. They can do this interchangeably because they suspend
their coroutines instead of blocking the thread. When the updateNews function is
waiting for a network response, the Main thread is used by updateProfile. Here, it’s
assumed that getUserData did not suspend because the user’s data was already cached,
therefore it can run until its completion. This wasn’t enough time for the network
response, so the main thread is not used at that time (it can be used by other functions).
Once the data appears, we grab the Main thread and use it on the updateNews function,
starting from the point straight after getNewsFromApi().

By definition, coroutines are components that can be sus-


pended and resumed. Concepts like async/await and gen-
erators, which can be found in languages like JavaScript,
Rust or Python, also use coroutines, but their capabili-
ties are very limited.

So, our first problem might be solved by using Kotlin coroutines in


the following way:

You might also like