Slide 1

Slide 1 text

Introduction to: Kotlin Coroutines For Asynchronous Programming on Android Nick Capurso

Slide 2

Slide 2 text

whoami Nick Capurso Capital One Wallet for Android @nickcapurso

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

Take an example... Simple Email App To retrieve user’s emails: ● Retrieve an OAuth token. ● Make the Emails API call.

Slide 6

Slide 6 text

Take an example... Simple Email App What makes this an interesting example?

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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.

Slide 9

Slide 9 text

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)...

Slide 10

Slide 10 text

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.

Slide 11

Slide 11 text

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.

Slide 12

Slide 12 text

Take an example... UI Layer Repository Cache Network

Slide 13

Slide 13 text

Take an example… UI Layer Repository Cache Network Retrieve Emails

Slide 14

Slide 14 text

Take an example... UI Layer Repository Cache Network Do OAuth Retrieve Emails

Slide 15

Slide 15 text

Take an example… UI Layer Repository Cache Network Render result on UI

Slide 16

Slide 16 text

Take an example... UI Layer Repository Cache Network

Slide 17

Slide 17 text

Take an example... class Repository(private val cache: Cache, private val network: Network) { fun retrieveOAuthToken() {} fun retrieveEmails() {} }

Slide 18

Slide 18 text

Take an example... fun retrieveEmails() { // Get an OAuth token (could be cached else involves a network call) // Use token to call the Emails API }

Slide 19

Slide 19 text

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.

Slide 20

Slide 20 text

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.

Slide 21

Slide 21 text

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.

Slide 22

Slide 22 text

Take an example… with callbacks fun retrieveOAuthToken(callback: (OAuthToken) -> Unit): Unit fun retrieveEmails(callback: (List) -> Unit): Unit

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Take an example… with callbacks fun retrieveEmails(callback: (List) -> 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”?

Slide 28

Slide 28 text

Take an example... fun retrieveEmails() { // Get an OAuth token (could be cached else involves a network call) // Use token to call the Emails API }

Slide 29

Slide 29 text

Take an example… with futures fun retrieveOAuthToken(): Future fun retrieveEmails(): Future>

Slide 30

Slide 30 text

Take an example… with futures fun retrieveEmails(): Future> { val token = retrieveOAuthToken().get() val emails = network.callEmailsApi(token).get() return executorService.submit> { // Do some extra processing // ... emails } }

Slide 31

Slide 31 text

Take an example… with futures fun retrieveEmails(): Future> { val token = retrieveOAuthToken().get() val emails = network.callEmailsApi(token).get() return executorService.submit> { // Do some extra processing // ... emails } }

Slide 32

Slide 32 text

Take an example… with futures fun retrieveEmails(): Future> { val token = retrieveOAuthToken().get() val emails = network.callEmailsApi(token).get() return executorService.submit> { // Do some extra processing // ... emails } }

Slide 33

Slide 33 text

Take an example… with futures val pendingEmails = repository.retrieveEmails() // Can wait with a timeout pendingEmails.get(5000, TimeUnit.SECONDS) // And also cancel if needed pendingEmails.cancel(true)

Slide 34

Slide 34 text

Take an example… others…? RxJava

Slide 35

Slide 35 text

Take an example… others…? RxJava ● Generally more powerful. ● Supports easy cancellation and lots of functionality through operators. ● Learning curve.

Slide 36

Slide 36 text

Take an example… others…? RxJava ● Generally more powerful. ● Supports easy cancellation and lots of functionality through operators. ● Learning curve. See Manuel Vivo’s talk at the next meeting for an RxJava-Coroutines comparison & interop.

Slide 37

Slide 37 text

Coroutines

Slide 38

Slide 38 text

What is a coroutine? “Coroutines are computer-program components that … [allows] multiple entry points for suspending and resuming execution at certain locations.” - Wikipedia

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

What is a coroutine? Because coroutines can suspend and resume execution:

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

What is a coroutine? Because coroutines can suspend and resume execution: ● A single thread can potentially do more work. ● You can write asychronous code that looks sequential. ○ Writable ○ Maintainable ○ Testable

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

What is a coroutine? fun retrieveEmails(): List { // 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

Slide 51

Slide 51 text

What is a coroutine? fun retrieveEmails(): List { // 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

Slide 52

Slide 52 text

What is a coroutine? fun retrieveEmails(): List { // 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

Slide 53

Slide 53 text

What is a coroutine? fun retrieveEmails(): List { // 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

Slide 54

Slide 54 text

What is a coroutine? fun retrieveEmails(): List { // 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

Slide 55

Slide 55 text

What is a coroutine? fun retrieveEmails(): List { // 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

Slide 56

Slide 56 text

What is a coroutine? fun retrieveEmails(): List { // 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

Slide 57

Slide 57 text

What is a coroutine? fun retrieveEmails(): List { // 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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

What is a coroutine? fun retrieveEmails(): List { // 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

Slide 61

Slide 61 text

What is a coroutine? fun retrieveEmails(): List { // 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

Slide 62

Slide 62 text

What is a coroutine? fun retrieveEmails(): List { // 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

Slide 63

Slide 63 text

What is a coroutine? fun retrieveEmails(): List { // 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

Slide 64

Slide 64 text

What is a coroutine? fun retrieveEmails(): List { // 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

Slide 65

Slide 65 text

What is a coroutine? fun retrieveEmails(): List { // 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

Slide 66

Slide 66 text

What is a coroutine? fun retrieveEmails(): List { // 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

Slide 67

Slide 67 text

What is a coroutine? fun retrieveEmails(): List { // 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

Slide 68

Slide 68 text

What is a coroutine? fun retrieveEmails(): List { // 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

Slide 69

Slide 69 text

What are Kotlin Coroutines? fun retrieveEmails(): List { // 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

Slide 70

Slide 70 text

What are Kotlin Coroutines? suspend fun retrieveEmails(): List { // 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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

What are Kotlin Coroutines? suspend fun retrieveEmails(): List Suspend functions can also directly return data even though the data may be retrieved asynchronously.

Slide 73

Slide 73 text

What are Kotlin Coroutines? suspend fun retrieveEmails(): List A combination of generated code + the coroutines dependencies is used to efficiently manage threads and suspend / resume coroutines.

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

What are Kotlin Coroutines? suspend fun retrieveEmails(search: String): List { // 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 }

Slide 77

Slide 77 text

What are Kotlin Coroutines? suspend fun retrieveOAuthToken(): OAuthToken { val cachedToken = cache.oAuthToken return if (cachedToken == null || cachedToken.isExpired) { val token = network.callOAuthApi() cache.oAuthToken = token token } else { cachedToken } }

Slide 78

Slide 78 text

Coroutine Builders non-suspend -> suspend Problem: you can’t call suspend functions from non-suspend functions. fun onClick() { val emails = repository.retrieveEmails() // ... }

Slide 79

Slide 79 text

Coroutine Builders non-suspend -> suspend In other words, only coroutines can call coroutines. How do you start a coroutine in the first place? fun onClick() { val emails = repository.retrieveEmails() // ... }

Slide 80

Slide 80 text

Coroutine Builders non-suspend -> suspend Use “coroutine builders” to start coroutines.

Slide 81

Slide 81 text

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.

Slide 82

Slide 82 text

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.

Slide 83

Slide 83 text

Coroutine Builders non-suspend -> suspend Use “coroutine builders” to start coroutines and allow you to call suspend functions: ● launch ● async ● runBlocking

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

Coroutine Builders - launch non-suspend -> suspend launch - returns immediately and executes the lambda on a separate thread* 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... } }

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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 ...

Slide 98

Slide 98 text

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 ... }

Slide 99

Slide 99 text

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() // ...

Slide 100

Slide 100 text

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() // ...

Slide 101

Slide 101 text

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() // ...

Slide 102

Slide 102 text

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() // ... }

Slide 103

Slide 103 text

Coroutine Builders - async non-suspend -> suspend … whereas this would just execute both calls sequentially launch { val emails = repository.retrieveEmails() val picture = repository.retrieveProfilePicture() // ... }

Slide 104

Slide 104 text

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 ... }

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

What are Kotlin Coroutines? suspending function calls non-suspend function Start coroutine: launch / async / runBlocking

Slide 107

Slide 107 text

Next ● Using thread pools ● Cancellation ● Error Handling ● Testing coroutines

Slide 108

Slide 108 text

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).

Slide 109

Slide 109 text

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 }

Slide 110

Slide 110 text

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 }

Slide 111

Slide 111 text

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 }

Slide 112

Slide 112 text

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 }

Slide 113

Slide 113 text

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()) { }

Slide 114

Slide 114 text

Using Thread Pools You can also create your own dispatcher of a fixed pool size. val fixedPool = newFixedThreadPoolContext(3, "fixed pool") launch(fixedPool) { }

Slide 115

Slide 115 text

A note about threading... Recall: using coroutines means functions are suspended (not blocked) and the same thread can be reused for something else.

Slide 116

Slide 116 text

A note about threading... Assume we have a single thread pool: val singlePool = newFixedThreadPoolContext(1, "single thread pool")

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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") }

Slide 120

Slide 120 text

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") }

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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 }

Slide 123

Slide 123 text

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.

Slide 124

Slide 124 text

Using Thread Pools The kotlinx-coroutines-android dependency adds the “UI” dispatcher launch(UI) { // Basically acts as runOnMainThread }

Slide 125

Slide 125 text

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() }

Slide 126

Slide 126 text

Using Thread Pools Though be careful... launch(UI) { val emails = repository.retrieveEmails() emailsAdapter.addAll(emails) emailsAdapter.notifyDataSetChanged() }

Slide 127

Slide 127 text

Using Thread Pools suspend fun retrieveEmails(): List { // 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

Slide 128

Slide 128 text

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()

Slide 129

Slide 129 text

Cancelling Coroutines Coroutine builders also return an item to represent the work-in-progress and also give you access to a cancel() function. val job: Job = launch { ... } job.cancel() val deferred: Deferred = async { ... } deferred.cancel()

Slide 130

Slide 130 text

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()

Slide 131

Slide 131 text

Cancelling Coroutines What does “cancelling” do? Built-in suspend functions (await(), delay(), etc.) understand cancellation and will stop suspending & quit the coroutine on cancellation.

Slide 132

Slide 132 text

Cancelling Coroutines val job = launch { repeat(5) { delay(2500) Log.d("Test", "Test") } } launch { delay(3000) job.cancel() } // Only prints "Test" once

Slide 133

Slide 133 text

Cancelling Coroutines What does “cancelling” do? Otherwise, you can check the isActive flag and exit yourself.

Slide 134

Slide 134 text

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

Slide 135

Slide 135 text

Cancelling Coroutines However, each usage of a builder starts an independent coroutine, regardless of whether or not you were already inside one. val job = launch { launch { // ... } // ... } job.cancel() // Only cancels the outer job.

Slide 136

Slide 136 text

Cancelling Coroutines Information about a coroutine is stored in a CoroutineContext and includes the dispatcher, the parent coroutine, etc. val job = launch { launch { // ... } // ... } job.cancel() // Only cancels the outer job.

Slide 137

Slide 137 text

Cancelling Coroutines … and is accessible inside a builder via coroutineContext.

Slide 138

Slide 138 text

Cancelling Coroutines Passing a coroutine context to another builder sets up a parent-child relationship and is respected for cancellation val job = launch { launch(coroutineContext) { // ... } // ... } job.cancel() // Now both coroutines are cancelled

Slide 139

Slide 139 text

Cancelling Coroutines It also means both jobs will use the same dispatcher. launch(UI) { launch(coroutineContext) { // Code in here runs on UI } // Code in here runs on UI }

Slide 140

Slide 140 text

Cancelling Coroutines You can “add” to a CoroutineContext to override certain properties (like the dispatcher), while maintaining the parent-child relationship. launch(UI) { launch(coroutineContext + CommonPool) { // Code in here runs on CommonPool, // but is still a child of the outer coroutine } // Code in here runs on UI }

Slide 141

Slide 141 text

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(rootParent) { async(rootParent) { } async(rootParent) { } }

Slide 142

Slide 142 text

Cancelling Coroutines launch { val emails = repository.fetchEmails() withContext(UI) { emailsAdapter.addAll(emails) emailsAdapter.notifyDataSetChanged() } } withContext executes within the same coroutine, no need to set up parent-child.

Slide 143

Slide 143 text

Error Handling Since you’re writing “sequentially executing” code now, you don’t have “onError” callbacks.

Slide 144

Slide 144 text

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”.

Slide 145

Slide 145 text

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 { data class Success(val result: T) : Result() data class Error(val exception: Exception) : Result() }

Slide 146

Slide 146 text

Error Handling suspend fun retrieveEmails(): Result> launch { val emailResult = repository.retrieveEmails() when (emailResult) { is Result.Success -> { } is Result.Error -> { } } }

Slide 147

Slide 147 text

Error Handling You can wrap a try-catch within a coroutine, if needed. launch { try { repository.retrieveEmails() } catch (e: Exception) { // ... } }

Slide 148

Slide 148 text

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

Slide 149

Slide 149 text

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(mock(), mock()) whenever(network.callEmailApi(any())).thenReturn(emailList) assertEquals(repository.retrieveEmails(), emailList) }

Slide 150

Slide 150 text

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(mock(), mock()) whenever(network.callEmailApi(any())).thenReturn(emailList) assertEquals(repository.retrieveEmails(), emailList) }

Slide 151

Slide 151 text

Testing Just one thing to take care of...

Slide 152

Slide 152 text

Testing Use runBlocking when you test suspend functions. @Test fun `Retrieve Emails`() = runBlocking { // Mock out OAuth too // ... val emailList = listOf(mock(), mock()) whenever(network.callEmailApi(any())).thenReturn(emailList) assertEquals(repository.retrieveEmails(), emailList) }

Slide 153

Slide 153 text

Testing But if you end with a verify call.... runBlocking also returns the result of the last function call. @Test fun `Retrieve Emails`() = runBlocking { // Mock out OAuth too // ... val emailList = listOf(mock(), mock()) whenever(network.callEmailApi(any())).thenReturn(emailList) assertEquals(repository.retrieveEmails(), emailList) verify(network).callEmailApi(any()) }

Slide 154

Slide 154 text

Testing … you get an error because the verify call will return something from runBlocking.

Slide 155

Slide 155 text

Testing You could add “Unit” as a solution. @Test fun `Retrieve Emails`(): Unit = runBlocking { // Mock out OAuth too // ... val emailList = listOf(mock(), mock()) whenever(network.callEmailApi(any())).thenReturn(emailList) assertEquals(repository.retrieveEmails(), emailList) verify(network).callEmailApi(any()) Unit }

Slide 156

Slide 156 text

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

Slide 157

Slide 157 text

Testing Instead, I like to use this helper method: fun testSuspending(test: suspend () -> Unit): Unit = runBlocking { test.invoke() } @Test fun `Retrieve Emails`() = testSuspending { // Mock out OAuth too // ... val emailList = listOf(mock(), mock()) whenever(network.callEmailApi(any())).thenReturn(emailList) assertEquals(repository.retrieveEmails(), emailList) verify(network).callEmailApi(any()) }

Slide 158

Slide 158 text

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

Slide 159

Slide 159 text

Testing Keep your CoroutineDispatcher somewhere 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.

Slide 160

Slide 160 text

Testing Keep your CoroutineDispatcher somewhere 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()

Slide 161

Slide 161 text

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 { // .. }

Slide 162

Slide 162 text

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) { // ... }

Slide 163

Slide 163 text

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) { // ... }

Slide 164

Slide 164 text

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) { // ... }

Slide 165

Slide 165 text

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

Slide 166

Slide 166 text

Tips for starting in an existing app Interop with existing libraries If an existing library returns data asynchronously, use suspendCoroutine to make it compatible to use with suspend functions.

Slide 167

Slide 167 text

Tips for starting in an existing app Interop with existing libraries If an existing library returns data asynchronously, 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

Slide 168

Slide 168 text

A Note on “Experimental” Status “Kotlin coroutines can and should be used in production.” - Roman Elizarov Tech Lead on Kotlin Coroutines @ Jetbrains

Slide 169

Slide 169 text

A Note on “Experimental” Status ● “Experimental” means they can release new stuff in minor updates. ● New updates will maintain backwards compatibility. ● “Unstable” or or WIP features are clearly marked / annotated.

Slide 170

Slide 170 text

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

Slide 171

Slide 171 text

Roman Elizarov @ KotlinConf ● Introduction to Coroutines ● Deep Dive into Coroutines on JVM Coroutines GitHub: https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md If you want more...

Slide 172

Slide 172 text

Stay tuned for the next Meetup! Manuel Vivo is handling a “Part 2” of this talk which covers advanced coroutine usage and RxJava comparisons!

Slide 173

Slide 173 text

Any Questions? @nickcapurso

Slide 174

Slide 174 text

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