Kotlin Coroutines and Android sitting in a tree
-
Upload
kai-koenig -
Category
Software
-
view
336 -
download
0
Transcript of Kotlin Coroutines and Android sitting in a tree
KOTLIN COROUTINES AND ANDROID SITTING IN A TREE...
KAI KOENIG (@AGENTK)
HELLO
HELLO
My name is Kai!
Software Architect in the web and mobile (Android) space from New Zealand.
Stuff I enjoy: Android, Kotlin, CFML, compilers and parsers, aviation and flying small aircraft, cats and chickens, Nintendo video games of all ages!
Twitter: @AgentK
AGENDA
AGENDA
▸ Kotlin in 5 minutes
▸ Why coroutines?
▸ Using coroutines in your Kotlin code
▸ Libraries and coroutines on Android
KOTLIN IN 5 MINUTES
https://www.flickr.com/photos/tschiae/8080742303/
HISTORY OF KOTLIN (I)
▸ Jetbrains wanted a more efficient JVM language when building products
▸ Looked at Scala, Groovy, etc. but came up with their own language spec
▸ First shown at the JVM Language Summit in 2011
▸ Got some traction in Android-land in 2014
▸ Modern language features (lambdas, HOF etc) but Java 6 byte code
▸ Low methods count (~7000)
▸ Strong focus on concision and an efficient, bloat-free language
KOTLIN IN 5 MINUTES
HISTORY OF KOTLIN (II)
▸ Since 1.0 release in early 2016:
▸ multiple maintenance releases
▸ now at 1.0.7
▸ 1.1 was released in Feb 2017,
▸ currently at 1.1.51 (previously 1.1.5-1)
KOTLIN IN 5 MINUTES
HISTORY OF KOTLIN (III)
▸ At Google IO 2017, Kotlin became a first-grade language for Android
▸ Development of 1.2 is underway
▸ Java 9 compatibility
▸ Multiplatform projects
▸ Huge improvements to StdLib, type inference, smart cast etc.
▸ Strong community, lots of interesting frameworks, awesome support from Jetbrains
KOTLIN IN 5 MINUTES
KOTLIN IN 5 MINUTES
PROMINENT LANGUAGE FEATURES
▸ Immutability
▸ String templates
▸ Explicit null handling
▸ Properties and Fields
▸ Data classes
▸ Extension functions
▸ Syntactic sugar
▸ Type inference
▸ Lambdas
▸ Collection API
▸ Type-safe builders
▸ Java-Kotlin-Interop
THE 1.1 RELEASE
OVERVIEW
▸ JavaScript target is not experimental anymore (browser & NodeJS)
▸ Full Kotlin language support
▸ Large part of the stdlib is supported
▸ Coroutines (JVM-only, experimental)
▸ Lightweight concurrency mechanism
▸ Alternative to threads
▸ Tooling improvements & more language features
WHY COROUTINES?
https://www.flickr.com/photos/61299367@N05/9448165205
WHY COROUTINES?
MOTIVATION
▸ Asynchronous programming is becoming increasingly important
▸ Problem: the need to avoid blocking introduces a lot of complexity
▸ Threads are:
▸ expensive to manage and limited
▸ complex in applications with lots of mutable state
▸ usually even more complex to deal with in UI-driven applications
▸ Callback hell (very common in Javascript)
http://slides.com/michaelholroyd/asyncnodejs/#/
WHY COROUTINES?
HISTORY
▸ Coroutines were first mentioned and used in Simula 67
▸ detach & resume keywords to suspend and then later resume execution
▸ Pushed aside by industry trend towards multi-threading
▸ C# has async/await
▸ Go was one of the first modern languages re-introducing coroutines
WHY COROUTINES?
COROUTINES IN KOTLIN (I)
▸ Kotlin’s approach: Suspending functions
▸ Function/lambda that can be suspended and resumed
▸ No context-switching on the OS level
▸ Minimal integration into the core language and stdlib, most of functionality provided by libraries
▸ Design allows for a variety of asynchronous API methodologies to be implemented on top of Kotlin coroutines
▸ Supposed to feel like writing traditional code
WHY COROUTINES?
COROUTINES IN KOTLIN (II)
▸ Easiest way to think about a coroutine is a very light-weighted thread
▸ They can run in parallel
▸ Coroutines can wait for each other
▸ They can communicate with each other
▸ Very, very cheap to create (compared to threads)
▸ A thread can run a lot of coroutines
WHY COROUTINES?
COROUTINES IN KOTLIN (III)
▸ Multiple layers of libraries and integration points:
▸ Language support: suspending functions
▸ Low-level library in kotlinx.coroutines
▸ Mid-level library in kotlinx.coroutines
▸ High-level libraries (Anko etc.)
USING COROUTINES IN YOUR KOTLIN CODE
https://www.flickr.com/photos/97114911@N05/14720054418
USING COROUTINES IN YOUR KOTLIN CODE
FUNDAMENTALS
▸ Use Kotlin 1.1.4 as a minimum, better 1.1.51
▸ Enable experimental feature in Gradle
kotlin { experimental { coroutines 'enable' } }
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.19.2'
USING COROUTINES IN YOUR KOTLIN CODE
“HELLO WORLD”
▸ launch {…} starts new coroutine on CommonPool thread pool
▸ delay() is a suspending function (not blocking a thread)
fun main(args: Array<String>) { println("Start on main thread")
launch(CommonPool) { delay(5000) println("Hello from coroutine") }
Thread.sleep(10000) println("Stop on main thread") }
USING COROUTINES IN YOUR KOTLIN CODE
“HELLO WORLD” IN BETTER
▸ Wait for the coroutines to finish - launch {…} returns a Job object
fun main(args: Array<String>) = runBlocking { println("Start on main thread")
val job = launch(CommonPool) { delay(5000) println("Hello from coroutine") }
job.join() println("Stop on main thread") }
USING COROUTINES IN YOUR KOTLIN CODE
THREADS VS COROUTINES
import java.util.concurrent.atomic.AtomicInteger import kotlin.concurrent.thread
fun main(args: Array<String>) { val c = AtomicInteger()
for (i in 1..1_000_000) thread(start = true) { c.addAndGet(i) }
println(c.get()) }
import kotlinx.coroutines.experimental.* import java.util.concurrent.atomic.AtomicInteger
fun main(args: Array<String>) { val c = AtomicInteger()
for (i in 1..1_000_000) launch(CommonPool) { c.addAndGet(i) }
// Temporary hack to allow the coroutines to finish Thread.sleep(2000) println(c.get()) }
USING COROUTINES IN YOUR KOTLIN CODE
THREADS VS COROUTINES
USING COROUTINES IN YOUR KOTLIN CODE
ASYNC/AWAIT (I)
▸ async {…} starts new coroutine and returns a Deferred<T> object
▸ Deferred<T>.await() returns result of the coroutine
▸ await() needs to be called from inside a coroutine, because it needs to suspend in a non-blocking way
▸ Solution: wrap into runBlocking {…} coroutine
▸ Starts coroutine and wait until it’s finished
USING COROUTINES IN YOUR KOTLIN CODE
ASYNC/AWAIT (II)
import kotlinx.coroutines.experimental.*
fun main(args: Array<String>) {
val deferred = (1..1_000_000).map { n -> async (CommonPool) { n } }
runBlocking { val sum = deferred.sumBy { it.await() } println("Sum: $sum") } }
USING COROUTINES IN YOUR KOTLIN CODE
SUSPEND FUNCTIONS (I)
▸ Coroutines can suspend without blocking a thread
▸ Functions that might suspend need to be marked with the suspend keyword
▸ They can only be called from another suspend function or a coroutine
USING COROUTINES IN YOUR KOTLIN CODE
SUSPEND FUNCTIONS (II)fun main(args: Array<String>) {
val deferred = (1..1_000_000).map { n -> async (CommonPool) { doWork(n) } }
runBlocking { ... } }
suspend fun doWork(n: Int) : Int { delay(50) return n }
USING COROUTINES IN YOUR KOTLIN CODE
BEHIND THE SCENES
▸ Kotlin coroutines are very light on language features:
▸ suspend the only new keyword added to the language and acts pretty much like a compiler flag
▸ Continuation and CoroutineContext in stdlib
▸ All the rest is in the kotlinx.coroutines library
This makes the design of Kotlin coroutines very composable.
USING COROUTINES IN YOUR KOTLIN CODE
MORE BEHIND THE SCENES
▸ At compilation:
▸ Suspending functions compile to functions with a general callback interface of type Continuation
▸ Code with suspension points compiles to state machine
▸ launch, runBlocking, async etc. are often called coroutine builders
USING COROUTINES IN YOUR KOTLIN CODE
LAUNCH VS ASYNC
▸ Conceptually very similar
▸ launch {…} returns a Job, no resulting value
▸ async {…} returns a Deferred - a future that can be used to obtain a value
▸ async generally suited better in situations with independent concurrent flows
USING COROUTINES IN YOUR KOTLIN CODE
WAITING AND CANCELLING
▸ cancel() and then join() can be used to terminate a coroutine execution early
▸ Job has an extension function cancelAndJoin() doing both at once
val job = launch { repeat(1000) { i -> println("job: I'm sitting here and delaying $i ...") delay(500L) } } delay(1300L) println("main: I'm really over waiting!") job.cancel() job.join() // or use: job.cancelAndJoin() println("main: Let's go.")
USING COROUTINES IN YOUR KOTLIN CODE
DEALING WITH TIMEOUTS
▸ Quite often the motivation for cancelling is a timeout:
▸ Track yourself via the Job instance
▸ Use withTimeout() {…}
withTimeout(1300L) { repeat(1000) { i -> println("I'm waiting $i ...") delay(500L) } }
USING COROUTINES IN YOUR KOTLIN CODE
THAT’S NOT ALL OF IT YET
▸ Coroutine context
▸ Coroutines and threads
▸ Channels
▸ Pipelines
▸ Dealing with state
▸ Shared (mutable) state & Actors
Compare Coroutines with Java Futures API
LIBRARIES AND COROUTINES ON ANDROID
https://www.flickr.com/photos/qiaomeng/4665885561/
MOTIVATION
▸ In general, UI-driven apps need to be aware of long-running processes
▸ In Android specifically, we can’t do any networking on the UI thread.
▸ The kotlinx.coroutines library by Jetbrains provides a starting point for Android, too.
LIBRARIES AND COROUTINES ON ANDROID
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-android:0.19.2'
UI CONTEXT
▸ The Android library provides access to a coroutine context for the UI thread
▸ Coroutine launches in UI thread, UI updates and suspending functions are possible
▸ Non-blocking, UI is not frozen
LIBRARIES AND COROUTINES ON ANDROID
launch(UI) { for (i in 10 downTo 1) { hello.text = "Countdown $i ..." delay(500) } hello.text = "Done!" }
OTHER UI THREAD CONCERNS
▸ For Jobs and cancelling coroutines, the same general principles still apply
▸ using launch {…} provides a reference to a Job
▸ can be cancelled with cancel() - for instance via UI control
▸ In UI scenarios useful to write own coroutines builders as extension functions
LIBRARIES AND COROUTINES ON ANDROID
button.onClick { ... }
fun View.onClick(action: suspend () -> Unit) { setOnClickListener { launch(UI) { action() } } }
OTHER UI THREAD CONCERNS
▸ Interesting topics for further study:
▸ Limit and manage coroutines via actors
▸ Dealing with event conflation
▸ Channel.CONFLATED
▸ Channel.UNLIMITED
LIBRARIES AND COROUTINES ON ANDROID
THREAD BLOCKING OPERATIONS AND THE UI
▸ CPU-intensive computations and/or API calls
▸ Can’t be done from UI thread or UI thread-confined coroutine
▸ Solution: suspending functions with execution context CommonPool
LIBRARIES AND COROUTINES ON ANDROID
suspend fun fib(x: Int): Int = run(CommonPool) { fibBlocking(x) }
fun fibBlocking(x: Int): Int = if (x <= 1) 1 else fibBlocking(x - 1) + fibBlocking(x - 2)
NETWORK CALLS (I)
▸ Callback-free API call, handle offline-exceptions
LIBRARIES AND COROUTINES ON ANDROID
fun getUsers() : Deferred<List<Users>> { return async(CommonPool) { val request = Request.Builder().url(<SOMEURL>).build() val response = OkHttpClient().newCall(request).execute() // parse response... } }
NETWORK CALLS (II)
▸ Handle exceptions in calling code
▸ Use result object’s await() to obtain the data (suspending the coroutine)
▸ Use launch {…} builder to trigger execution of getUsers
LIBRARIES AND COROUTINES ON ANDROID
launch(UI) { try { val result = getUsers() adapter.setElements(result.await()) ... } catch (exception: IOException){ // we’re offline }
ANKO
▸ More often than not identified with declarative UI for Android/Kotlin
▸ But it also has APIs for:
▸ Async
▸ SQLite
▸ Anko 0.9 introduced naming changes around the Async API
▸ Since Anko 0.10, Anko has support for coroutines
LIBRARIES AND COROUTINES ON ANDROID
LIBRARIES AND COROUTINES ON ANDROID
COROUTINES IN ANKO
▸ Add anko-coroutines dependency
▸ Earlier versions of Anko already had support for async handling
▸ New:
▸ Coroutines in listeners
▸ bg()
▸ asReference()
fun getData(): Data { ... } fun showData(data: Data) { ... }
async(UI) { val data: Deferred<Data> = bg { // Runs on the background getData() }
// This code is executed on the UI thread showData(data.await()) }
LIBRARIES AND COROUTINES ON ANDROID
COROUTINES WITH ASYNCAWAIT
▸ Async/await approach
▸ Very rich library on top of Kotlin’s coroutine core
▸ Plugins for Retrofit and rxJava
async { val result = await { //Long running code } // Use result }
async { val repos = await { github.getRepos() } showList(repos) repos.forEach { repo -> val stats = await { github.getStats (repo.name) } showStats(repo, stats) } }
OTHER THINGS AND FINAL THOUGHTS
https://www.flickr.com/photos/chrispiascik/4054331891
OTHER THINGS & FINAL THOUGHTS
UNIT TESTING SUSPENDING FUNCTIONS
▸ They need a coroutine to run, easiest way seems to be with runBlocking {…}
import kotlinx.coroutines.experimental.runBlocking import org.testng.annotations.Test
class MyTest { @Test fun testMySuspendingFunction() = runBlocking<Unit> { // your test code here } }
OTHER THINGS & FINAL THOUGHTS
EXPERIMENTAL?
▸ Coroutines are experimental in Kotlin 1.1.x, however:
▸ Very sound approach of dealing with concurrency
▸ Jetbrains guarantees backwards compatibility
▸ Potentially no forward compatibility
▸ Coroutines can and should be used in production
OTHER THINGS
RESOURCES
▸ Coroutines design document:https://github.com/Kotlin/kotlin-coroutines
▸ Coroutines guide:https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md
▸ Java Futures & Coroutines:https://blog.frankel.ch/concurrency-java-futures-kotlin-coroutines/#gsc.tab=0
▸ Anko: https://github.com/Kotlin/anko
▸ AsyncAwait: https://github.com/metalabdesign/AsyncAwait
OTHER THINGS
GET IN TOUCH
Kai Koenig
Email: [email protected]
Work: http://www.ventego-creative.co.nz
Twitter: @AgentK
Telegram: @kaikoenig
Slides: http://www.slideshare.com/agentk