Slide 1

Slide 1 text

Introduction to Kotlin Coroutines by Mayowa Adegeye (@mayojava) @Belindroid

Slide 2

Slide 2 text

● AsyncTask

Slide 3

Slide 3 text

● AsyncTask ● Service/IntentService

Slide 4

Slide 4 text

● AsyncTask ● Service/IntentService ● Loaders & Handlers

Slide 5

Slide 5 text

● AsyncTask ● Service/IntentService ● Loaders & Handlers ● Callbacks

Slide 6

Slide 6 text

● AsyncTask ● Service/IntentService ● Loaders & Handlers ● Callbacks ● RxJava

Slide 7

Slide 7 text

fun login(username: String, password: String): User

Slide 8

Slide 8 text

fun login(username: String, password: String): User fun getOrders(userId: String): List

Slide 9

Slide 9 text

fun login(username: String, password: String): User fun getOrders(userId: String): List fun fetchUserOrders() { val user = login(username, password) val orders = getOrders(user.userId) showUserOrders(orders) }

Slide 10

Slide 10 text

fun login(username: String, password: String): User fun getOrders(userId: String): List fun fetchUserOrders() { val user = login(username, password) val orders = getOrders(user.userId) showUserOrders(orders) } I/O calls

Slide 11

Slide 11 text

fun login(username: String, password: String): User fun getOrders(userId: String): List fun fetchUserOrders() { val user = login(username, password) val orders = getOrders(user.userId) showUserOrders(orders) } UI thread

Slide 12

Slide 12 text

Callback fun fetchUserOrders(username: String, password: String) { login(username, password) { user -> getOrders(user.userId) { orders -> showUserOrders(orders) } } }

Slide 13

Slide 13 text

RxJava fun fetchUserOrders(username: String, password: String) { login(username, password) .flatMap { user → getOrders(user.userId) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.main()) .subscribe({orders -> showUserOrders(orders)}) }

Slide 14

Slide 14 text

Is it possible to write async code in a sequential manner without getting lost in the implementation details ?

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

with COROUTINES

Slide 17

Slide 17 text

What is a coroutine

Slide 18

Slide 18 text

What is a coroutine ● A lightweight thread that can run a computation without blocking.

Slide 19

Slide 19 text

What is a coroutine ● A lightweight thread that can run a computation without blocking. ● It can be suspended and resumed at a later time.

Slide 20

Slide 20 text

What is a coroutine ● A lightweight thread that can run a computation without blocking. ● It can be suspended and resumed at a later time. ● Suspension points are defined by the programmer.

Slide 21

Slide 21 text

Key Coroutine Concepts

Slide 22

Slide 22 text

Key Coroutine Concepts ● Suspending functions

Slide 23

Slide 23 text

Key Coroutine Concepts ● Suspending functions ● Coroutine Dispatchers

Slide 24

Slide 24 text

Key Coroutine Concepts ● Suspending functions ● Coroutine Dispatchers ● Coroutine Builders

Slide 25

Slide 25 text

Suspending function ● A function that can suspend the execution of a coroutine

Slide 26

Slide 26 text

Suspending function ● A function that can suspend the execution of a coroutine ● It can be started, paused and resumed at a later time

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

Suspending function...contd ● They can only be called within a coroutine or from another suspending function

Slide 30

Slide 30 text

Suspending function ● They can only be called within a coroutine or from another suspending function ● They can take parameters and have return types

Slide 31

Slide 31 text

Syntax suspend fun doHeavyComputation(param: Int): Int { //heavy computation body }

Slide 32

Slide 32 text

fun doHeavyComputation(param: Int,callback:Continuation)

Slide 33

Slide 33 text

fun doHeavyComputation(param: Int,callback:Continuation) interface Continuation { public val context: CoroutineContext public fun resumeWith(result: Result) } fun Continuation.resume(value: T) { } fun Continuation.resumeWithException(exception: Throwable)

Slide 34

Slide 34 text

Coroutine Dispatchers

Slide 35

Slide 35 text

Coroutine Dispatchers ● CoroutineContext Element that determines the thread a coroutine runs on.

Slide 36

Slide 36 text

Coroutine Dispatchers ● CoroutineContext Element that determines the thread a coroutine runs on. ● It can confine a coroutine to a specific thread, a threadpool or let it run unconfined.

Slide 37

Slide 37 text

Kotlinx.coroutines dispatchers ● Dispatchers.DEFAULT

Slide 38

Slide 38 text

Kotlinx.coroutines dispatchers ● Dispatchers.DEFAULT ● Dispatchers.IO

Slide 39

Slide 39 text

Kotlinx.coroutines dispatchers ● Dispatchers.DEFAULT ● Dispatchers.IO ● Dispatchers.UNCONFINED

Slide 40

Slide 40 text

Kotlinx.coroutines dispatchers ● Dispatchers.DEFAULT ● Dispatchers.IO ● Dispatchers.UNCONFINED ● Dispatchers.MAIN

Slide 41

Slide 41 text

Kotlinx.coroutines dispatchers ● Dispatchers.DEFAULT ● Dispatchers.IO ● Dispatchers.UNCONFINED ● Dispatchers.MAIN ● newFixedThreadPoolContext & newSingleThreadContext

Slide 42

Slide 42 text

CoroutineContext Element ● Job

Slide 43

Slide 43 text

CoroutineContext Element ● Job ● ContinuationInterceptor

Slide 44

Slide 44 text

CoroutineContext Element ● Job ● ContinuationInterceptor ● CoroutineExceptionHandler

Slide 45

Slide 45 text

CoroutineContext Element ● Job ● ContinuationInterceptor ● CoroutineExceptionHandler ● CoroutineName

Slide 46

Slide 46 text

Coroutine Builders

Slide 47

Slide 47 text

Coroutine Builders Acts as the bridge that allows us to call suspending functions from our code.

Slide 48

Slide 48 text

Coroutine Builders Acts as the bridge that allows us to call suspending functions from our code. ● launch - fire and forget

Slide 49

Slide 49 text

Coroutine Builders Acts as the bridge that allows us to call suspending functions from our code. ● launch - fire and forget ● async - returns a Deferred

Slide 50

Slide 50 text

launch fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, Start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job

Slide 51

Slide 51 text

async fun CoroutineScope.async( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T ): Deferred

Slide 52

Slide 52 text

Async ● You need to call await() on the returned Deferred to get the actual result.

Slide 53

Slide 53 text

Async ● You need to call await() on the returned Deferred to get the actual result. ● async...await can be used to run several tasks in parallel.

Slide 54

Slide 54 text

suspend fun firstValue(): Int { delay(500) //delays for 500ms return 10 }

Slide 55

Slide 55 text

suspend fun firstValue(): Int { delay(500) //delays for 500ms return 10 } suspend fun secondValue(): Int { delay(300) //delays for 300ms return 20 }

Slide 56

Slide 56 text

suspend fun firstValue(): Int { delay(500) //delays for 500ms return 10 } suspend fun secondValue(): Int { delay(300) //delays for 300ms return 20 } fun main() = runBlocking { val time = measureTimeMillis { val first = async { firstValue() } val second = async { secondValue() } val sum = first.await() + second.await() } println(time) }

Slide 57

Slide 57 text

suspend fun firstValue(): Int { delay(500) //delays for 500ms return 10 } suspend fun secondValue(): Int { delay(300) //delays for 300ms return 20 } fun main() = runBlocking { val time = measureTimeMillis { val first = async { firstValue() } val second = async { secondValue() } val sum = first.await() + second.await() } println(time) }

Slide 58

Slide 58 text

suspend fun firstValue(): Int { delay(500) //delays for 500ms return 10 } suspend fun secondValue(): Int { delay(300) //delays for 300ms return 20 } fun main() = runBlocking { val time = measureTimeMillis { val first = async { firstValue() } val second = async { secondValue() } val sum = first.await() + second.await() } println(time) }

Slide 59

Slide 59 text

suspend fun firstValue(): Int { delay(500) //delays for 500ms return 10 } suspend fun secondValue(): Int { delay(300) //delays for 300ms return 20 } fun main() = runBlocking { val time = measureTimeMillis { val first = async { firstValue() } val second = async { secondValue() } val sum = first.await() + second.await() } println(time) //prints 518 }

Slide 60

Slide 60 text

withContext Can be used to run a block of code that returns in a value in another context.

Slide 61

Slide 61 text

withContext Can be used to run a block of code that returns in a value in another context. It suspends until completion and returns the result

Slide 62

Slide 62 text

withContext public suspend fun withContext( context: CoroutineContext, block: suspend CoroutineScope.() -> T ): T

Slide 63

Slide 63 text

Get user orders revisited fun login(username: String, password: String): User fun getOrders(userId: String): List fun fetchUserOrders() { val user = login(username, password) val orders = getOrders(user.userId) showUserOrders(orders) }

Slide 64

Slide 64 text

Get user orders revisited suspend fun login(username: String, password: String): User { return withContext(Dispatcher.IO) { //returns User } }

Slide 65

Slide 65 text

Get user orders revisited suspend fun login(username: String, password: String): User { return withContext(Dispatcher.IO) { //returns User } } suspend fun getOrders(userId: String): List { return withContext(Dispatchers.IO) { //return list of orders } }

Slide 66

Slide 66 text

Get user orders revisited fun fetchUserOrders() = GlobalScope.launch(Dispatchers.MAIN){ val user = login(username, password) val orders = getOrders(user.userId) showUserOrders(orders) }

Slide 67

Slide 67 text

Get user orders revisited fun fetchUserOrders() = GlobalScope.launch(Dispatchers.MAIN){ val user = login(username, password) //first suspension point val orders = getOrders(user.userId) showUserOrders(orders) }

Slide 68

Slide 68 text

Get user orders revisited fun fetchUserOrders() = GlobalScope.launch(Dispatchers.MAIN){ val user = login(username, password) val orders = getOrders(user.userId) //second suspension point showUserOrders(orders) }

Slide 69

Slide 69 text

Get user orders revisited fun fetchUserOrders() = GlobalScope.launch(Dispatchers.MAIN){ val user = login(username, password) val orders = getOrders(user.userId) showUserOrders(orders) } back on main thread and can update the UI

Slide 70

Slide 70 text

Structured concurrency ● All coroutine builders are extension function of CoroutineScope

Slide 71

Slide 71 text

Structured concurrency ● All coroutine builders are extension function of CoroutineScope ● We need to be able to keep track of all coroutines launched

Slide 72

Slide 72 text

Structured concurrency ● All coroutine builders are extension function of CoroutineScope ● We need to be able to keep track of all coroutines launched ● CoroutineScope should be implemented by an object with a lifeycle i.e Activity, Fragment, ViewModel

Slide 73

Slide 73 text

class MainActivity: AppCompatActivity() { private fun loadData() { } }

Slide 74

Slide 74 text

class MainActivity: AppCompatActivity(), CoroutineScope { private fun loadData() { } }

Slide 75

Slide 75 text

class MainActivity: AppCompatActivity(), CoroutineScope { override val coroutineContext: CoroutineContext get() = Dispatchers.Main private fun loadData() { } }

Slide 76

Slide 76 text

class MainActivity: AppCompatActivity(), CoroutineScope { private lateint var job override val coroutineContext: CoroutineContext get() = Dispatchers.Main private fun loadData() { } }

Slide 77

Slide 77 text

class MainActivity: AppCompatActivity(), CoroutineScope { private lateint var job override val coroutineContext: CoroutineContext get() = Dispatchers.Main override fun onCreate(savedInstanceState: Bundle?) { job = Job() } private fun loadData() { } }

Slide 78

Slide 78 text

class MainActivity: AppCompatActivity(), CoroutineScope { private lateint var job override val coroutineContext: CoroutineContext get() = Dispatchers.Main override fun onCreate(savedInstanceState: Bundle?) { job = Job() } override fun onDestroy() { job.cancel() } }

Slide 79

Slide 79 text

class MainActivity: AppCompatActivity(), CoroutineScope { private lateint var job override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job override fun onCreate(savedInstanceState: Bundle?) { job = Job() } override fun onDestroy() { job.cancel() } }

Slide 80

Slide 80 text

class MainActivity: AppCompatActivity(), CoroutineScope { private lateint var job override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job override fun onCreate(savedInstanceState: Bundle?) { job = Job() } override fun onDestroy() { job.cancel() } }

Slide 81

Slide 81 text

Conventions ● Functions declared as extension function on CoroutineScope should return immediately and executes its task concurrently with the rest of the program.

Slide 82

Slide 82 text

Conventions ● Functions declared as extension function on CoroutineScope should return immediately and executes its task concurrently with the rest of the program. ● Suspending functions should do its task separately in another context and return to caller when done. It should not block the caller.

Slide 83

Slide 83 text

Conventions ● Suspending functions should do its task separately in another context and return to caller when done. It should not block the caller. ● Functions should not be both suspending and an extension function on coroutine scope.

Slide 84

Slide 84 text

Don’t do this suspend fun CoroutineScope.launchBackgroundWork()

Slide 85

Slide 85 text

Do this fun CoroutineScope.launchBackgroundWork() = launch { }

Slide 86

Slide 86 text

Do this fun CoroutineScope.launchBackgroundWork() = launch { } suspend fun doIOWork() = withContext(Dispatchers.IO) { }

Slide 87

Slide 87 text

Exception Handling ● Wrap suspend functions or deferred.await() in a try..catch block

Slide 88

Slide 88 text

Exception Handling ● Wrap suspend functions or deferred.await() in a try..catch block ● Launch coroutine builder propagates exceptions as an unhandled exception, while async exposes the exception to the user to consume.

Slide 89

Slide 89 text

val job = GlobalScope.launch { println("Throwing exception from launch") throw IndexOutOfBoundsException() } val deferred = GlobalScope.async { println("Throwing exception from async") throw ArithmeticException() }

Slide 90

Slide 90 text

val job = GlobalScope.launch { println("Throwing exception from launch") throw IndexOutOfBoundsException() //exception will be printed to console } val deferred = GlobalScope.async { println("Throwing exception from async") throw ArithmeticException() // Nothing is printed, relying on user to call await }

Slide 91

Slide 91 text

val deferred = GlobalScope.async { println("Throwing exception from async") throw ArithmeticException() // Nothing is printed, relying on user to call await } try { deferred.await() println("Unreached") } catch (e: ArithmeticException) { println("Caught ArithmeticException") //printed to the console }

Slide 92

Slide 92 text

use CoroutineExceptionHandler context element as a generic handler for all uncaught exceptions.

Slide 93

Slide 93 text

val exceptionHandler = CoroutineExceptionHandler { _, exception -> println(“Caught: $exception") }

Slide 94

Slide 94 text

val exceptionHandler = CoroutineExceptionHandler { _, exception -> println(“Caught: $exception") } val job = GlobalScope.launch(exceptionHandler) { }

Slide 95

Slide 95 text

val exceptionHandler = CoroutineExceptionHandler { _, exception -> println(“Caught: $exception") } val job = GlobalScope.launch(exceptionHandler) { throw IOException(“error occurred”) } //console output: Caught: java.io.IOException: error occurred

Slide 96

Slide 96 text

CoroutineExceptionHandler is only invoked on exceptions that are not expected to be handled by the user

Slide 97

Slide 97 text

val exceptionHandler = CoroutineExceptionHandler { _, exception -> println(“Caught: $exception") } val job = GlobalScope.async(exceptionHandler) { throw IndexOutOfBoundsException(“error occurred”) } //exception will be swallowed and nothing printed to the console

Slide 98

Slide 98 text

Testing Make your viewmodels, use cases or repositories explicitly take a Dispatcher and provide a replacement in your test.

Slide 99

Slide 99 text

data class DispatcherProvider( val io: CoroutineDispatcher, val computation: CoroutineDispatcher, val main: CoroutineDispatcher )

Slide 100

Slide 100 text

data class DispatcherProvider( val io: CoroutineDispatcher, val computation: CoroutineDispatcher, val main: CoroutineDispatcher ) @Provides @Singleton fun providesDispatchersProvider() = DispatcherProvider( io = Dispatchers.IO, computation = Dispatchers.Default, main = Dispatchers.Main )

Slide 101

Slide 101 text

class FetchMoviesUseCase(private val moviesApi: MoviesApi, private val dispatcherProvider: DispatcherProvider) { suspend fun movies(): List { return withContext(dispatcherProvider.io) { moviesApi.getMovies() } } }

Slide 102

Slide 102 text

use Unconfined Dispatcher in test @Provides @Singleton fun providesTestDispatchers() = DispatcherProvider( io = Dispatchers.Unconfined, computation = Dispatchers.Unconfined, main = Dispatchers.Unconfined )

Slide 103

Slide 103 text

TestCoroutineContext ● use TestCoroutineContext if you deal with delays and timeout in your test.

Slide 104

Slide 104 text

TestCoroutineContext ● use TestCoroutineContext if you deal with delays and timeout in your test. ● Similar to TestScheduler in RxJava

Slide 105

Slide 105 text

TestCoroutineContext ● use TestCoroutineContext if you deal with delays and timeout in your test. ● Similar to TestScheduler in RxJava ● advanceTimeBy

Slide 106

Slide 106 text

TestCoroutineContext ● use TestCoroutineContext if you deal with delays and timeout in your test. ● Similar to TestScheduler in RxJava ● advanceTimeBy ● advanceTimeTo

Slide 107

Slide 107 text

TestCoroutineContext ● use TestCoroutineContext if you deal with delays and timeout in your test. ● Similar to TestScheduler in RxJava ● advanceTimeBy ● advanceTimeTo ● triggerActions

Slide 108

Slide 108 text

Topics not covered ● Channels ● Coroutines and RxJava interoperability

Slide 109

Slide 109 text

Further Reading Google Coroutines code lab Kotlin Coroutines guide Structured Concurrency by Roman Elizarov