Coroutines 2
Coroutines 2
7
Why Kotlin Coroutines? 8
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).
fun onCreate() {
val news = getNewsFromApi()
val sortedNews = news
.sortedByDescending { it.publishedAt }
view.showNews(sortedNews)
}
Thread switching
fun onCreate() {
thread {
val news = getNewsFromApi()
val sortedNews = news
.sortedByDescending { it.publishedAt }
runOnUiThread {
view.showNews(sortedNews)
}
}
}
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)
}
}
fun onCreate() {
startedCallbacks += getNewsFromApi { news ->
val sortedNews = news
.sortedByDescending { it.publishedAt }
view.showNews(sortedNews)
}
}
fun showNews() {
getConfigFromApi { config ->
getNewsFromApi(config) { news ->
getUserFromApi { user ->
view.showNews(user, news)
}
}
}
}
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.
fun onCreate() {
disposables += getNewsFromApi()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map { news ->
news.sortedByDescending { it.publishedAt }
}
.subscribe { sortedNews ->
view.showNews(sortedNews)
}
}
fun onCreate() {
val news = getNewsFromApi()
val sortedNews = news
.sortedByDescending { it.publishedAt }
view.showNews(sortedNews)
}
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
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().