Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin Coroutines: An Intro to Practical Use on Android

Kotlin Coroutines: An Intro to Practical Use on Android

Presented at 360|AnDev 2018:
https://360andev.com/sessions/kotlin-coroutines-an-intro-to-practical-use-on-android/

Slightly longer version given at a Meetup is also on Speaker Deck: https://speakerdeck.com/nickcapurso/introduction-to-kotlin-coroutines-for-asynchronous-programming-on-android

Abstract:
Asynchronous programming has always been a challenging problem for Android developers. Any good solution has to gracefully handle errors, cancellation, chaining multiple actions together, handle thread swapping, and much more.

Kotlin’s coroutines are yet another option for asynchronous programming. However, unlike other popular contenders, coroutines meet all the previously mentioned requirements out-of-the-box, have a gentler learning curve, and don’t require you to write an absurd amount of boilerplate.

That said, learning any new asynchronous solution takes time and often comes with a period of trial-and-error until you have all the nuances figured out. Get a jumpstart on coroutines:

(1) Lessen the learning curve by getting the subtleties of coroutines out of the way early
(2) Learn how to work coroutines into an existing app: easy places to start transitioning, interop with existing libraries, and reusing existing threading constructs
(3) Learn how to easily support error handling, cancellation, and task chaining
(4) Learn how to unit test coroutines

Get a head start on coroutines in your apps and easily write, test, and maintain asynchronous code like never before!

Nick Capurso

July 19, 2018
Tweet

More Decks by Nick Capurso

Other Decks in Technology

Transcript

  1. whoareyou This talk is intended for those who: • Are

    Android developers* with Kotlin experience • Have familiarity with asynchronous programming, especially for making API calls. • Have some experience at least one of: ◦ AsyncTask (Android) ◦ Executor ◦ Future ◦ RxJava
  2. Outline • Framing example • Issues with current async solutions

    • Intro to Kotlin Coroutines ◦ What is a “coroutine”? ◦ Main usage & concepts ◦ Thread pools ◦ Cancellation ◦ Error handling ◦ Testing • Tips for usage in an existing app
  3. Take an example... Simple Email App To retrieve user’s emails:

    • Retrieve an OAuth token. • Make the Emails API call.
  4. Take an example... Simple Email App What makes this an

    interesting example? • An OAuth token can be retrieved asynchronously from the network…
  5. Take an example... Simple Email App What makes this an

    interesting example? • An OAuth token can be retrieved asynchronously from the network… • … or synchronously if stored in a cache.
  6. Take an example... Simple Email App What makes this an

    interesting example? • An OAuth token can be retrieved asynchronously from the network… • … or synchronously if stored in a cache. • The “retrieve emails” call can potentially kick off two API calls (OAuth + Emails)...
  7. Take an example... Simple Email App What makes this an

    interesting example? • An OAuth token can be retrieved asynchronously from the network… • … or synchronously if stored in a cache. • The “retrieve emails” call can potentially kick off two API calls (OAuth + Emails)... • … and either one could fail.
  8. Take an example... Simple Email App What makes this an

    interesting example? • An OAuth token can be retrieved asynchronously from the network… • … or synchronously if stored in a cache. • The “retrieve emails” call can potentially kick off two API calls (OAuth + Emails)... • … and either one could fail. • Finally, you may want to cancel either call.
  9. Take an example... class Repository(private val cache: Cache, private val

    network: Network) { fun retrieveOAuthToken() {} fun retrieveEmails() {} }
  10. Take an example... fun retrieveEmails() { // Get an OAuth

    token (could be cached else involves a network call) // Use token to call the Emails API }
  11. Take an example... How might we implement this? 1. Need

    to notify caller of an asynchronous result. 2. Need an abstraction for writing asynchronous code. 3. Other needs: a. Support making one call dependent on another (OAuth -> Emails). b. Support cancellation.
  12. Take an example... How might we implement this? 1. Need

    to notify caller of an asynchronous result. 2. Need an abstraction for writing asynchronous code. 3. Other needs: a. Support making one call dependent on another (OAuth -> Emails). b. Support cancellation.
  13. Take an example... How might we implement this? 1. Need

    to notify caller of an asynchronous result. 2. Need an abstraction for writing asynchronous code. 3. Other needs: a. Support making one call dependent on another (OAuth -> Emails). b. Support cancellation.
  14. Take an example… with callbacks fun retrieveOAuthToken(callback: (OAuthToken) -> Unit):

    Unit fun retrieveEmails(callback: (List<Email>) -> Unit): Unit
  15. Take an example… with callbacks fun retrieveEmails(callback: (List<Email>) -> Unit):

    Unit { // Get an OAuth Token retrieveOAuthToken { token -> network.callEmailsApi(token) { emails -> // Maybe do some processing on the list ... // ... callback.invoke(emails) } } }
  16. Take an example… with callbacks fun retrieveEmails(callback: (List<Email>) -> Unit):

    Unit { // Get an OAuth Token retrieveOAuthToken { token -> network.callEmailsApi(token) { emails -> // Maybe do some processing on the list ... // ... callback.invoke(emails) } } }
  17. Take an example… with callbacks fun retrieveEmails(callback: (List<Email>) -> Unit):

    Unit { // Get an OAuth Token retrieveOAuthToken { token -> network.callEmailsApi(token) { emails -> // Maybe do some processing on the list ... // ... callback.invoke(emails) } } }
  18. Take an example… with callbacks fun retrieveEmails(callback: (List<Email>) -> Unit):

    Unit { // Get an OAuth Token retrieveOAuthToken { token -> network.callEmailsApi(token) { emails -> // Maybe do some processing on the list ... // ... callback.invoke(emails) } } }
  19. Take an example… with callbacks fun retrieveEmails(callback: (List<Email>) -> Unit):

    Unit { // Get an OAuth Token retrieveOAuthToken { token -> network.callEmailsApi(token) { emails -> // Maybe do some processing on the list ... // ... callback.invoke(emails) } } } How do we “cancel”?
  20. Take an example... fun retrieveEmails() { // Get an OAuth

    token (could be cached else involves a network call) // Use token to call the Emails API }
  21. Take an example… with Future fun retrieveOAuthToken(): Future<OAuthToken> fun retrieveEmails():

    Future<List<Email>> • Support out-of-the box for Android is lacking (Java 7 Futures): ◦ No way to (easily) compose multiple operations. • Supports cancelation and waiting with a timeout.
  22. Take an example… with RxJava fun retrieveOAuthToken(): Single<OAuthToken> fun retrieveEmails():

    Single<List<Email>> • Powerful, easy to compose multiple operations, supports cancelation, thread pools, error handling, etc. • High learning curve. • See the extra links at the end of the talk -- RxJava vs. Coroutines
  23. What is a coroutine? “Coroutines are computer-program components that …

    [allows] multiple entry points for suspending and resuming execution at certain locations.” - Wikipedia
  24. What is a coroutine? A function that can stop executing

    (“suspend”) and continue where it left off* later. *implies some kind of state is kept
  25. What is a coroutine? A function that can stop executing

    (“suspend”) and continue where it left off* later. *implies some kind of state is kept
  26. What is a coroutine? A function that can stop executing

    (“suspend”) and continue where it left off* later. *implies some kind of state is kept
  27. What is a coroutine? A function that is “blocking” stops

    the thread from performing other work. Diagrams: https://medium.com/@elye.project/understanding-suspend-function-of-coroutines-de26b070c5ed
  28. What is a coroutine? A function that is “suspending” is

    not blocking. Other work can be done on the same thread. Diagrams: https://medium.com/@elye.project/understanding-suspend-function-of-coroutines-de26b070c5ed
  29. What is a coroutine? A function that is “suspending” is

    not blocking. Other work can be done on the same thread. Diagrams: https://medium.com/@elye.project/understanding-suspend-function-of-coroutines-de26b070c5ed
  30. What is a coroutine? Because coroutines can suspend and resume

    execution: • A single thread can potentially do more work.
  31. What is a coroutine? Because coroutines can suspend and resume

    execution: • A single thread can potentially do more work. • You can write asynchronous code that looks sequential.
  32. What is a coroutine? Because coroutines can suspend and resume

    execution: • A single thread can potentially do more work. • You can write asynchronous code that looks sequential. ◦ Writable ◦ Maintainable ◦ Testable
  33. What is a coroutine? fun retrieveEmails(): List<Email> { // Get

    an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails }
  34. What is a coroutine? fun retrieveEmails(): List<Email> { // Get

    an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails } 1 2 3 4 5 6 7 8 9 10 11
  35. What is a coroutine? fun retrieveEmails(): List<Email> { // Get

    an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails } 1 2 3 4 5 6 7 8 9 10 11
  36. What is a coroutine? fun retrieveEmails(): List<Email> { // Get

    an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails } 1 2 3 4 5 6 7 8 9 10 11
  37. What is a coroutine? fun retrieveEmails(): List<Email> { // Get

    an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails } 1 2 3 4 5 6 7 8 9 10 11
  38. What is a coroutine? fun retrieveEmails(): List<Email> { // Get

    an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails } 1 2 3 4 5 6 7 8 9 10 11 T
  39. What is a coroutine? fun retrieveEmails(): List<Email> { // Get

    an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails } 1 2 3 4 5 6 7 8 9 10 11
  40. What is a coroutine? fun retrieveEmails(): List<Email> { // Get

    an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails } 1 2 3 4 5 6 7 8 9 10 11 suspend at line 3
  41. What is a coroutine? fun retrieveEmails(): List<Email> { // Get

    an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails } 1 2 3 4 5 6 7 8 9 10 11 suspend at line 3
  42. What is a coroutine? While the function is suspended waiting

    for the response, we can actually run something else on the same thread. suspend at line 3
  43. What is a coroutine? fun retrieveProfilePicture(): Bitmap { // ...

    } While the function is suspended waiting for the response, we can actually run something else on the same thread. suspend at line 3
  44. What is a coroutine? fun retrieveEmails(): List<Email> { // Get

    an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails } 1 2 3 4 5 6 7 8 9 10 11 resume at line 3
  45. What is a coroutine? fun retrieveEmails(): List<Email> { // Get

    an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails } 1 2 3 4 5 6 7 8 9 10 11 resume at line 3
  46. What is a coroutine? fun retrieveEmails(): List<Email> { // Get

    an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails } 1 2 3 4 5 6 7 8 9 10 11
  47. What is a coroutine? fun retrieveEmails(): List<Email> { // Get

    an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails } 1 2 3 4 5 6 7 8 9 10 11 suspend at line 6
  48. What is a coroutine? fun retrieveEmails(): List<Email> { // Get

    an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails } 1 2 3 4 5 6 7 8 9 10 11 suspend at line 6
  49. What is a coroutine? fun retrieveEmails(): List<Email> { // Get

    an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails } 1 2 3 4 5 6 7 8 9 10 11 resume at line 6
  50. What is a coroutine? fun retrieveEmails(): List<Email> { // Get

    an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails } 1 2 3 4 5 6 7 8 9 10 11
  51. What is a coroutine? fun retrieveEmails(): List<Email> { // Get

    an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails } 1 2 3 4 5 6 7 8 9 10 11
  52. What is a coroutine? fun retrieveEmails(): List<Email> { // Get

    an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails } 1 2 3 4 5 6 7 8 9 10 11
  53. What are Kotlin Coroutines? fun retrieveEmails(): List<Email> { // Get

    an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails } 1 2 3 4 5 6 7 8 9 10 11
  54. What are Kotlin Coroutines? suspend fun retrieveEmails(): List<Email> { //

    Get an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails } 1 2 3 4 5 6 7 8 9 10 11
  55. What are Kotlin Coroutines? compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.23.4' // More on this

    later... kotlin { experimental { coroutines 'enable' } } Added to your project via Gradle:
  56. What are Kotlin Coroutines? suspend fun retrieveEmails(): List<Email> Enables the

    use of the keyword “suspend” to mark functions that may suspend (e.x. to do an asynchronous action)
  57. What are Kotlin Coroutines? suspend fun retrieveEmails(): List<Email> Suspend functions

    can also directly return data even though the data may be retrieved asynchronously.
  58. What are Kotlin Coroutines? suspend fun retrieveEmails(): List<Email> A combination

    of generated code + the coroutines dependencies is used to efficiently manage threads and suspend / resume coroutines.
  59. What are Kotlin Coroutines? suspend fun retrieveOAuthToken(): OAuthToken suspend fun

    retrieveEmails(): List<Email> class Network { suspend fun callEmailsApi(token: OAuthToken): List<Email> The catch: suspend functions can only be called from other suspend functions.
  60. What are Kotlin Coroutines? suspend fun retrieveEmails(): List<Email> { //

    Get an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails }
  61. What are Kotlin Coroutines? suspend fun retrieveEmails(search: String): List<Email> {

    // Get an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing … val filtered = emails.filter { it.title.contains(search) } return filtered }
  62. Coroutine Builders non-suspend -> suspend Problem: you can’t call suspend

    functions from non-suspend functions. fun onClick() { val emails = repository.retrieveEmails() // ... }
  63. Coroutine Builders non-suspend -> suspend In other words, only coroutines

    can call suspend functions. How do you start a coroutine in the first place? fun onClick() { val emails = repository.retrieveEmails() // ... }
  64. Coroutine Builders non-suspend -> suspend Use “coroutine builders” to start

    coroutines. All coroutine builders takes in a lambda and runs that lambda as a coroutine.
  65. Coroutine Builders non-suspend -> suspend Use “coroutine builders” to start

    coroutines. All coroutine builders takes in a lambda and runs that lambda as a coroutine -- i.e. enables you to call suspend functions.
  66. Coroutine Builders non-suspend -> suspend Use “coroutine builders” to start

    coroutines and allow you to call suspend functions: • launch • async • runBlocking
  67. Coroutine Builders - launch non-suspend -> suspend launch - returns

    immediately and executes the lambda on a separate thread* (as a coroutine) fun onClick() { // Want to call retrieveEmails(), but need to be in a suspend function / // coroutine first }
  68. Coroutine Builders - launch non-suspend -> suspend launch - returns

    immediately and executes the lambda on a separate thread* (as a coroutine) fun onClick() { // Want to call retrieveEmails(), but need to be in a suspend function / // coroutine first launch { // I'm now within a coroutine, so I can call suspend functions val emails = repository.retrieveEmails() // Do something with the emails... } }
  69. Coroutine Builders - launch non-suspend -> suspend fun onClick() {

    // ... launch { // This code executes as a coroutine val emails = repository.retrieveEmails() // Process emails / display on UI } // ... } 1 2 3 4 5 6 7 8 9 10 11 12
  70. Coroutine Builders - launch non-suspend -> suspend fun onClick() {

    // ... launch { // This code executes as a coroutine val emails = repository.retrieveEmails() // Process emails / display on UI } // ... } 1 2 3 4 5 6 7 8 9 10 11 12
  71. Coroutine Builders - launch non-suspend -> suspend fun onClick() {

    // ... launch { // This code executes as a coroutine val emails = repository.retrieveEmails() // Process emails / display on UI } // ... } 1 2 3 4 5 6 7 8 9 10 11 12
  72. Coroutine Builders - launch non-suspend -> suspend fun onClick() {

    // ... launch { // This code executes as a coroutine val emails = repository.retrieveEmails() // Process emails / display on UI } // ... } 1 2 3 4 5 6 7 8 9 10 11 12
  73. Coroutine Builders - launch non-suspend -> suspend fun onClick() {

    // ... launch { // This code executes as a coroutine val emails = repository.retrieveEmails() // Process emails / display on UI } // ... } 1 2 3 4 5 6 7 8 9 10 11 12 launch
  74. Coroutine Builders - launch non-suspend -> suspend fun onClick() {

    // ... launch { // This code executes as a coroutine val emails = repository.retrieveEmails() // Process emails / display on UI } // ... } 1 2 3 4 5 6 7 8 9 10 11 12 launch
  75. Coroutine Builders - launch non-suspend -> suspend fun onClick() {

    // ... launch { // This code executes as a coroutine val emails = repository.retrieveEmails() // Process emails / display on UI } // ... } 1 2 3 4 5 6 7 8 9 10 11 12 launch suspend at line 6
  76. Coroutine Builders - launch non-suspend -> suspend fun onClick() {

    // ... launch { // This code executes as a coroutine val emails = repository.retrieveEmails() // Process emails / display on UI } // ... } 1 2 3 4 5 6 7 8 9 10 11 12 launch suspend at line 6
  77. Coroutine Builders - launch non-suspend -> suspend fun onClick() {

    // ... launch { // This code executes as a coroutine val emails = repository.retrieveEmails() // Process emails / display on UI } // ... } 1 2 3 4 5 6 7 8 9 10 11 12 launch resume at line 6
  78. Coroutine Builders - launch non-suspend -> suspend fun onClick() {

    // ... launch { // This code executes as a coroutine val emails = repository.retrieveEmails() // Process emails / display on UI } // ... } 1 2 3 4 5 6 7 8 9 10 11 12 launch
  79. Coroutine Builders - async non-suspend -> suspend async - returns

    a Deferred, which acts just like a Future. fun foo() { async { repository.retrieveEmails() } }
  80. Coroutine Builders - async non-suspend -> suspend async - returns

    a Deferred, which acts just like a Future. fun foo() { val pendingEmails: Deferred<List<Email>> = async { repository.retrieveEmails() } }
  81. Coroutine Builders - async non-suspend -> suspend async - returns

    a Deferred, which acts just like a Future. Call await() to get the data. val emails = pendingEmails.await() // Do something with emails ...
  82. Coroutine Builders - async non-suspend -> suspend async - returns

    a Deferred, which acts just like a Future. Call await() to get the data. Though, await() is a suspend function. launch { val emails = pendingEmails.await() // Do something with emails ... }
  83. Coroutine Builders - async non-suspend -> suspend Async is useful

    for running suspend functions in parallel. val pendingEmails = async { repository.retrieveEmails() } val pendingPicture = async { repository.retrieveProfilePicture() } val emails = pendingEmails.await() val picture = pendingPicture.await() // ...
  84. Coroutine Builders - async non-suspend -> suspend Async is useful

    for running suspend functions in parallel. val pendingEmails = async { repository.retrieveEmails() } val pendingPicture = async { repository.retrieveProfilePicture() } val emails = pendingEmails.await() val picture = pendingPicture.await() // ...
  85. Coroutine Builders - async non-suspend -> suspend Async is useful

    for running suspend functions in parallel. val pendingEmails = async { repository.retrieveEmails() } val pendingPicture = async { repository.retrieveProfilePicture() } val emails = pendingEmails.await() val picture = pendingPicture.await() // ...
  86. Coroutine Builders - async non-suspend -> suspend Async is useful

    for running suspend functions in parallel. launch { val pendingEmails = async { repository.retrieveEmails() } val pendingPicture = async { repository.retrieveProfilePicture() } val emails = pendingEmails.await() val picture = pendingPicture.await() // ... }
  87. Coroutine Builders - async non-suspend -> suspend … whereas this

    would just execute both calls sequentially launch { val emails = repository.retrieveEmails() val picture = repository.retrieveProfilePicture() // ... }
  88. Coroutine Builders - runBlocking non-suspend -> suspend Blocks the current

    thread until the coroutine completes. runBlocking { // Maybe I'm expecting these to be cached, so no suspending will be done... val emails = repository.retrieveEmails() // Do something with emails ... }
  89. Coroutine Builders - runBlocking non-suspend -> suspend Blocks the current

    thread until the coroutine completes. runBlocking { // Maybe I'm expecting these to be cached, so no suspending will be done... val emails = repository.retrieveEmails() // Do something with emails ... } // Code down here doesn’t run until runBlocking completes
  90. Using Thread Pools You can specify a thread pool (“Coroutine

    Dispatcher”) to use when using a coroutine builder. The default is the “CommonPool” builtin (of size numProcessors - 1).
  91. Using Thread Pools You can specify a thread pool (“Coroutine

    Dispatcher”) to use when using a coroutine builder. The default is the “CommonPool” builtin (of size numProcessors - 1). launch(CommonPool) { // Code in here runs on the CommonPool }
  92. Using Thread Pools You can specify a thread pool (“Coroutine

    Dispatcher”) to use when using a coroutine builder. The default is the “CommonPool” builtin (of size numProcessors - 1). launch(CommonPool) { // Code in here runs on the CommonPool }
  93. Using Thread Pools You can specify a thread pool (“Coroutine

    Dispatcher”) to use when using a coroutine builder. The default is the “CommonPool” builtin (of size numProcessors - 1). On Android -- careful with single & dual-core devices + CommonPool! launch(CommonPool) { // Code in here runs on the CommonPool }
  94. Using Thread Pools You can specify a thread pool (“Coroutine

    Dispatcher”) to use when using a coroutine builder. The default is the “CommonPool” builtin (of size numProcessors - 1). On Android -- careful with single & dual-core devices + CommonPool! • Solution - use your own thread pool of an appropriate size. launch(CommonPool) { // Code in here runs on the CommonPool }
  95. Using Thread Pools If you already have a thread pool,

    you can convert it to a Coroutine Dispatcher with a built-in extension function. val executor = AsyncTask.THREAD_POOL_EXECUTOR launch(executor.asCoroutineDispatcher()) { }
  96. Using Thread Pools You can also create your own dispatcher

    of a fixed pool size. val fixedPool = newFixedThreadPoolContext(3, "fixed pool") launch(fixedPool) { }
  97. A note about threading... Recall: using coroutines means functions are

    suspended (not blocked) and the same thread can be reused for something else.
  98. A note about threading... Assume we have a single thread

    pool: val singlePool = newFixedThreadPoolContext(1, "single thread pool")
  99. A note about threading... launch(singlePool) { // Blocking sleep Thread.sleep(5000)

    Log.d("Test", "1") } launch(singlePool) { Log.d("Test", "2") }
  100. A note about threading... launch(singlePool) { // Blocking sleep Thread.sleep(5000)

    Log.d("Test", "1") } launch(singlePool) { Log.d("Test", "2") }
  101. A note about threading... launch(singlePool) { // Blocking sleep Thread.sleep(5000)

    Log.d("Test", "1") } launch(singlePool) { Log.d("Test", "2") } // Output: [5 second delay] 1 2
  102. A note about threading... launch(singlePool) { // Part of coroutines

    -- suspend-version of sleep delay(5000) Log.d("Test", "1") } launch(singlePool) { Log.d("Test", "2") }
  103. A note about threading... launch(singlePool) { // Part of coroutines

    -- suspend-version of sleep delay(5000) Log.d("Test", "1") } launch(singlePool) { Log.d("Test", "2") }
  104. A note about threading... launch(singlePool) { // Part of coroutines

    -- suspend-version of sleep delay(5000) Log.d("Test", "1") } launch(singlePool) { Log.d("Test", "2") } // Output: 2 [5 second delay] 1
  105. Using Thread Pools Finally, there is “Unconfined” which basically gives

    you synchronous behavior*. // Code up here runs on the same thread ... launch(Unconfined) { // ... as code in here }
  106. Using Thread Pools Finally, there is “Unconfined” which basically gives

    you synchronous behavior*. *Note: Unconfined does not switch back to the original thread if the thread ever changes throughout execution of your coroutine.
  107. Using Thread Pools The UI dispatcher is also useful for

    updating views after running suspend functions. launch(UI) { val emails = repository.retrieveEmails() emailsAdapter.addAll(emails) emailsAdapter.notifyDataSetChanged() }
  108. Using Thread Pools Though be careful... launch(UI) { val emails

    = repository.retrieveEmails() emailsAdapter.addAll(emails) emailsAdapter.notifyDataSetChanged() }
  109. Using Thread Pools suspend fun retrieveEmails(): List<Email> { // Get

    an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails } … this code is now going to run on the UI thread as well, risking a NetworkOnMainThreadException
  110. Switching Threads in a Coroutine launch { val emails =

    repository.retrieveEmails() withContext(UI) { emailsAdapter.addAll(emails) emailsAdapter.notifyDataSetChanged() } } If you do need to switch threads within a coroutine, use withContext(<thread pool>)
  111. Cancelling Coroutines Coroutine builders also return an object to represent

    the work-in-progress and also give you access to a cancel() function. val job: Job = launch { ... } job.cancel() val deferred: Deferred<String> = async { ... } deferred.cancel()
  112. Cancelling Coroutines You can also use these objects to check

    the status of the coroutine. // Can check status, if needed job.isActive job.isCompleted job.isCancelled // Or cancel the job job.cancel()
  113. Cancelling Coroutines What does “cancelling” do? Built-in suspend functions (await(),

    delay(), etc.) understand cancellation and will stop suspending & quit the coroutine on cancellation.
  114. Cancelling Coroutines val job = launch { repeat(5) { delay(2500)

    Log.d("Test", "Test") } } launch { delay(3000) job.cancel() } // Only prints "Test" once
  115. Cancelling Coroutines val job = launch { while (isActive) {

    sleep(2500) // Blocking Log.d("Test", "Test") } } launch { delay(3000) job.cancel() } // Actually prints "Test" twice before quitting
  116. Cancelling Coroutines “Advanced” cancelation • Canceling nested jobs together •

    Canceling multiple, separate jobs at the same time (i.e. RxJava disposables).
  117. Cancelling Coroutines You can also set up a “master” parent

    and have all your coroutines draw from it, for easy cancellation. // All coroutines will have this as its parent. val rootParent = Job() // ... launch(parent = rootParent) { … } launch(parent = rootParent) { … } // Cancels both of the above job rootParent.cancel()
  118. Error Handling Since you’re writing “sequentially executing” code now, you

    don’t have “onError” callbacks. You might want to think about coming up with a data wrapper to represent “success” and “failure”.
  119. Error Handling Since you’re writing “sequentially executing” code now, you

    don’t have “onError” callbacks. You might want to think about coming up with a data wrapper to represent “success” and “failure”. sealed class Result<T> { data class Success<T>(val result: T) : Result<T>() data class Error(val exception: Exception) : Result<Nothing>() }
  120. Error Handling suspend fun retrieveEmails(): Result<List<Email>> { … } launch

    { val emailResult = repository.retrieveEmails() when (emailResult) { is Result.Success -> { } is Result.Error -> { } } }
  121. Testing The added benefit of writing code that is sequential

    is that it’s easy to test. suspend fun retrieveEmails(): List<Email> { // Get an OAuth token val token = retrieveOAuthToken() // Use token to call the Emails API val emails = network.callEmailsApi(token) // Do some processing ... return emails }
  122. Testing The added benefit of writing code that is sequential

    is that it’s easy to test. @Test fun `Retrieve Emails`() { // Mock out OAuth too // ... val emailList = listOf<Email>(mock(), mock()) whenever(network.callEmailApi(any())).thenReturn(emailList) assertEquals(repository.retrieveEmails(), emailList) }
  123. Testing The added benefit of writing code that is sequential

    is that it’s easy to test. @Test fun `Retrieve Emails`() { // Mock out OAuth too // ... val emailList = listOf<Email>(mock(), mock()) whenever(network.callEmailApi(any())).thenReturn(emailList) assertEquals(repository.retrieveEmails(), emailList) }
  124. Testing Use runBlocking when you test suspend functions. @Test fun

    `Retrieve Emails`() = runBlocking { // Mock out OAuth too // ... val emailList = listOf<Email>(mock(), mock()) whenever(network.callEmailApi(any())).thenReturn(emailList) assertEquals(repository.retrieveEmails(), emailList) }
  125. Testing Instead, I like to use this helper method, to

    ensure Unit is returned. fun testSuspending(test: suspend () -> Unit): Unit = runBlocking { test() }
  126. Testing Instead, I like to use this helper method, to

    ensure Unit is returned. fun testSuspending(test: suspend () -> Unit): Unit = runBlocking { test() }
  127. Testing Instead, I like to use this helper method, to

    ensure Unit is returned. @Test fun `Retrieve Emails`() = testSuspending { // Mock out OAuth too // ... val emailList = listOf<Email>(mock(), mock()) whenever(network.callEmailApi(any())).thenReturn(emailList) assertEquals(repository.retrieveEmails(), emailList) }
  128. Testing Keep your CoroutineDispatcher somewhere easily accessible, like a Dagger

    graph, in case you need to replace it during tests.
  129. Testing Keep your CoroutineDispatcher somewhere easily accessible, like a Dagger

    graph, in case you need to replace it during tests. You may need to replace it with “Unconfined” during unit tests to get synchronous / sequential execution of background coroutines.
  130. Testing Keep your CoroutineDispatcher somewhere easily accessible, like a Dagger

    graph, in case you need to replace it during tests. You may need to replace it with “Unconfined” during unit tests to get synchronous / sequential execution of background coroutines. For Android Espresso UI tests, swap in: AsyncTask.THREAD_POOL_EXECUTOR.asCoroutineDispatcher()
  131. Tips for starting in an existing app Replace usages of

    Thread { … }.start() • Efficiency benefit by letting Kotlin handle when to explicitly start a new thread. Thread { // .. }.start() launch { // .. }
  132. Tips for starting in an existing app Same for ExecutorService

    usages. • Convert to a CoroutineDispatcher with asCoroutineDispatcher() • execute -> launch • submit -> async executor.execute { // ... } val future = executor.submit { // ... } val dispatcher = executor.asCoroutineDispatcher() launch(dispatcher) { // ... } val deferred = async(dispatcher) { // ... }
  133. Tips for starting in an existing app Same for ExecutorService

    usages. • Convert to a CoroutineDispatcher with asCoroutineDispatcher() • execute -> launch • submit -> async executor.execute { // ... } val future = executor.submit { // ... } val dispatcher = executor.asCoroutineDispatcher() launch(dispatcher) { // ... } val deferred = async(dispatcher) { // ... }
  134. Tips for starting in an existing app Same for ExecutorService

    usages. • Convert to a CoroutineDispatcher with asCoroutineDispatcher() • execute -> launch • submit -> async executor.execute { // ... } val future = executor.submit { // ... } val dispatcher = executor.asCoroutineDispatcher() launch(dispatcher) { // ... } val deferred = async(dispatcher) { // ... }
  135. Tips for starting in an existing app Interop with existing

    libraries Retrofit (Android): • Jake Wharton’s Deferred adapter: https://github.com/JakeWharton/retrofit2-kotlin-coroutines-adapter
  136. Tips for starting in an existing app Interop with existing

    libraries If you want to adapt an existing library to coroutines, use suspendCoroutine to make it compatible to use with suspend functions.
  137. Tips for starting in an existing app Interop with existing

    libraries If you want to adapt an existing library to coroutines, use suspendCoroutine to make it compatible to use with suspend functions. • Allows you to suspend a coroutine “manually” handle when to resume execution. • https://www.youtube.com/watch?v=YrrUCSi72E8
  138. A Note on “Experimental” Status compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.23.4' // Need to

    “enable” the experimental feature to avoid warnings kotlin { experimental { coroutines 'enable' } }
  139. A Note on “Experimental” Status “Kotlin coroutines can and should

    be used in production.” - Roman Elizarov Tech Lead on Kotlin Coroutines @ Jetbrains
  140. A Note on “Experimental” Status • “Experimental” means they can

    release new stuff in minor updates. • New updates will maintain backwards compatibility. • “Unstable” or WIP features are clearly marked / annotated.
  141. A Note on “Experimental” Status Roman Elizarov on production usage:

    • https://stackoverflow.com/questions/46240236/can-experimental-kotli n-coroutines-be-used-in-production • https://www.slideshare.net/elizarov/introduction-to-kotlin-coroutines/ 33 • https://github.com/Kotlin/kotlinx.coroutines/issues/110
  142. Kotlin Coroutines on Android: Things I Wish I Knew at

    the Beginning: https://medium.com/capital-one-developers/kotlin-coroutines-on-android-things-i-wish-i-knew-at-the-begi nning-c2f0b1f16cff Coroutines and RxJava — An Asynchronicity Comparison (Part 1): https://medium.com/capital-one-developers/coroutines-and-rxjava-an-asynchronicity-comparison-part-1-a synchronous-programming-e726a925342a If you want more...
  143. Talks by Roman Elizarov @ KotlinConf • Introduction to Coroutines

    • Deep Dive into Coroutines on JVM Coroutines Guide on GitHub: https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md Parent-child coroutines / canceling coroutines together: https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md#children-of-a-coroutine https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md#cancellation-via-explicit-jo b If you want more...
  144. Any Questions? • Write functions that suspend and resume execution

    -- perform asynchronous actions using code that looks sequential / synchronous. • Asynchronous code is easier to write, test, and maintain. • A single thread can perform more work in the same amount of time. • Coroutines can work alongside existing asynchronous solutions and support can be written / provided for popular libraries. @nickcapurso