Slide 1

Slide 1 text

Dive into Async apps with Kotlin Coroutines

Slide 2

Slide 2 text

@aditlal Adit Lal Product Engineer @rivuchakraborty Rivu Chakraborty Android Engineer

Slide 3

Slide 3 text

Problem • Run multiple tasks • Async is easy doable

Slide 4

Slide 4 text

Asynchronous • Threading • AsyncTask • Callbacks • Observable • Future/Promise • Coroutines

Slide 5

Slide 5 text

Threads • Threads are expensive • occupies about 1-2 mb • Requires context switching • Thread management • Race conditions • Thread Pools

Slide 6

Slide 6 text

Callbacks • Difficulty of nested callbacks • Callback Hell • Error Handling is a mess

Slide 7

Slide 7 text

Future/Promise • Similar to callbacks • Composition model with chained calls • Complicated Exception handling • Returns Promise

Slide 8

Slide 8 text

Reactive • Observable streams • Constant API across platforms • Great error handling

Slide 9

Slide 9 text

Coroutines • Launch a long-running operation w/o blocking thread • Functions are now suspend-able • Platform independent

Slide 10

Slide 10 text

Coroutines simplify asynchronous programming by providing possibility to write code in direct style (sequentially).

Slide 11

Slide 11 text

./fetch_data

Slide 12

Slide 12 text

Blocking Thread Time Function A Blocked Blocked Function B

Slide 13

Slide 13 text

Suspended fun Thread Time Function A Suspended Function B

Slide 14

Slide 14 text

fun loadData() = GlobalScope.launch(uiContext) { showLoading() // ui thread val result = dataProvider.fetchData() // non ui thread, suspend until finished showText(result) // ui thread hideLoading() // ui thread } Suspend

Slide 15

Slide 15 text

fun loadData() = GlobalScope.launch(uiContext) { showLoading() // ui thread val result = dataProvider.fetchData() // non ui thread, suspend until finished showText(result) // ui thread hideLoading() // ui thread }

Slide 16

Slide 16 text

fun loadData() = GlobalScope.launch(uiContext) { showLoading() // ui thread val result = dataProvider.fetchData() // non ui thread, suspend until finished showText(result) // ui thread hideLoading() // ui thread }

Slide 17

Slide 17 text

fun loadData() = GlobalScope.launch(uiContext) { showLoading() // ui thread val result = dataProvider.fetchData() // non ui thread, suspend until finished showText(result) // ui thread hideLoading() // ui thread }

Slide 18

Slide 18 text

fun loadData() = GlobalScope.launch(uiContext) { showLoading() // ui thread val result = dataProvider.fetchData() // non ui thread, suspend until finished showText(result) // ui thread hideLoading() // ui thread }

Slide 19

Slide 19 text

CoroutineScope CoroutineContext Coroutine Builder launch

Slide 20

Slide 20 text

Context fun main() = runBlocking { println("My context is: $coroutineContext") } My context is: [CoroutineId(1), "coroutine#1":BlockingCoroutine{Active}@73c6c3b2, BlockingEventLoop@48533e64]

Slide 21

Slide 21 text

Scope interface CoroutineScope { val coroutineContext: CoroutineContext }

Slide 22

Slide 22 text

‘Dat Sequential suspend fun doSomethingUsefulOne(): Int { delay(1000L) //some fancy magic return 13 } suspend fun doSomethingUsefulTwo(): Int { delay(1000L) //some fancy magic again return 29 }

Slide 23

Slide 23 text

‘Dat Sequential val time = measureTimeMillis { val one = doSomethingUsefulOne() val two = doSomethingUsefulTwo() println("The answer is ${one + two}") } println("Completed in $time ms") The answer is 42 Completed in 2007 ms

Slide 24

Slide 24 text

‘Dat async val time = measureTimeMillis { val one = async(IO){ doSomethingUsefulOne() } val two = async(IO) { doSomethingUsefulTwo() } println("The answer is ${one.await() + two.await()}") } println("Completed in $time ms") The answer is 42 Completed in 1014 ms

Slide 25

Slide 25 text

Dispatcher To specify where the coroutines should run, Kotlin provides three Dispatchers you can use for thread dispatch.

Slide 26

Slide 26 text

Dispatchers.Main Main thread on Android, interact with the UI and perform light work Great for : - Calling suspend functions - Call UI functions - Updating LiveData

Slide 27

Slide 27 text

Dispatchers.IO Optimized for disk and network IO off the main thread Great for : - Database* - Reading/writing files - Networking$**

Slide 28

Slide 28 text

Dispatchers.Default Optimized for CPU intensive work off the main thread Great for : - Sorting a list - Parsing JSON - DiffUtils

Slide 29

Slide 29 text

Where thou Coroutines • One Shot request • Stream request

Slide 30

Slide 30 text

class SearchViewModel(val repo: SearchRepository): ViewModel() { private val _sortedResults = MutableLiveData>() val sortedResults: LiveData> = _sortedResults fun onSortAscending() = sortSearchBy(sort = true) fun onSortDescending() = sortSearchBy(sort = false) fun sortSearchBy(sort: Boolean, query: String) { viewModelScope.launch { // suspend and resume make this database request main-safe // so our ViewModel doesn't need to worry about threading _sortedResults.value = repo.fetchSearchResults(query, sort) } } }

Slide 31

Slide 31 text

class SearchViewModel(val repo: SearchRepository): ViewModel() { private val _sortedResults = MutableLiveData>() val sortedResults: LiveData> = _sortedResults fun onSortAscending() = sortSearchBy(ascending = true) fun onSortDescending() = sortSearchBy(ascending = false) fun sortSearchBy(sort: Boolean, query: String) { viewModelScope.launch { // suspend and resume make this database request main-safe // so our ViewModel doesn't need to worry about threading _sortedResults.value = repo.fetchSearchResults(query, sort) } } }

Slide 32

Slide 32 text

class SearchViewModel(val repo: SearchRepository): ViewModel() { private val _sortedResults = MutableLiveData>() val sortedResults: LiveData> = _sortedResults fun onSortAscending() = sortSearchBy(ascending = true) fun onSortDescending() = sortSearchBy(ascending = false) fun sortSearchBy(sort: Boolean, query: String) { viewModelScope.launch { // suspend and resume make this database request main-safe // so our ViewModel doesn't need to worry about threading _sortedResults.value = repo.fetchSearchResults(query, sort) } } }

Slide 33

Slide 33 text

class SearchViewModel(val repo: SearchRepository): ViewModel() { private val _sortedResults = MutableLiveData>() val sortedResults: LiveData> = _sortedResults fun onSortAscending() = sortSearchBy(ascending = true) fun onSortDescending() = sortSearchBy(ascending = false) fun sortSearchBy(sort: Boolean, query: String) { viewModelScope.launch { // suspend and resume make this database request main-safe // so our ViewModel doesn't need to worry about threading _sortedResults.value = repo.fetchSearchResults(query, sort) } } }

Slide 34

Slide 34 text

As a general pattern, start coroutines in the ViewModel.

Slide 35

Slide 35 text

class SearchRepository(val api: SearchAPI) { suspend fun fetchSearchResults( query: String, sort: Boolean ): List { api.fetchResults(query, sort) .map(::mapToList) } }

Slide 36

Slide 36 text

class SearchRepository(val api: SearchAPI) { suspend fun fetchSearchResults( query: String, sort: Boolean ): List { api.fetchResults(query, sort) .map(::mapToList) } }

Slide 37

Slide 37 text

class SearchRepository(val api: SearchAPI) { suspend fun fetchSearchResults( query: String, sort: Boolean ): List { api.fetchResults(query, sort) .map(::mapToList) } }

Slide 38

Slide 38 text

//Rx fun fetchResults( query: String, sort: Boolean ): Single //Coroutine suspend fun fetchResults( query: String, sort: Boolean ): SearchResponse

Slide 39

Slide 39 text

//Rx fun fetchResults( query: String, sort: Boolean ): Single //Coroutine suspend fun fetchResults( query: String, sort: Boolean ): SearchResponse

Slide 40

Slide 40 text

//Rx fun fetchResults( query: String, sort: Boolean ): Single //Coroutine suspend fun fetchResults( query: String, sort: Boolean ): SearchResponse

Slide 41

Slide 41 text

//Fast and local fetch fun foo(param: Param) : Result //Slow and IO suspend fun foo(param: Param) : Result //BG fun CoroutineScope.foo(param: Param): Result

Slide 42

Slide 42 text

Asynchronicity Comparison • Observables in RxJava Observable.create { emitter -> for (i in 1..5) { emitter.onNext(i) } emitter.onComplete() }

Slide 43

Slide 43 text

Asynchronicity Comparison • Channels val channel = Channel() launch { // this might be heavy CPU-consuming computation or async logic //we'll just send five squares for (x in 1..5) channel.send(x) } // here we print five received integers: repeat(5) { println(channel.receive()) } println("Done!")

Slide 44

Slide 44 text

Asynchronicity Comparison • Channels val channel = Channel() launch { // this might be heavy CPU-consuming computation or async logic //we'll just send five squares for (x in 1..5) channel.send(x) channel.close() } // here we print five received integers: repeat(5) { println(channel.receive()) } println("Done!")

Slide 45

Slide 45 text

Asynchronicity Comparison • Channels val channel = Channel() launch { // this might be heavy CPU-consuming computation or async logic //we'll just send five squares for (x in 1..5) channel.send(x) channel.close() } // here we print five received integers: repeat(5) { println(channel.receive()) } println("Done!")

Slide 46

Slide 46 text

Asynchronicity Comparison • Channels val channel = Channel() launch { // this might be heavy CPU-consuming computation or async logic //we'll just send five squares for (x in 1..5) channel.send(x) channel.close() } // here we print five received integers: repeat(5) { println(channel.receive()) } println("Done!")

Slide 47

Slide 47 text

Asynchronicity Comparison • Channels val sequence = produceSequence() sequence.consumeEach { println(it) } println("Done!")

Slide 48

Slide 48 text

Asynchronicity Comparison • Channels ? Observable ? When you consume an object from the channel no other coroutine will be able to get the same object

Slide 49

Slide 49 text

Asynchronicity Comparison • Channels ~ Race Conditions ? val channel = Channel() launch { val value1 = channel.receive() } launch { val value2 = channel.receive() } launch { channel.send(1) }

Slide 50

Slide 50 text

Asynchronicity Comparison • Rx Subjects val subject: PublishSubject = PublishSubject.create() subject.subscribe { consumeValue(it) } subject.subscribe { println(it) }

Slide 51

Slide 51 text

Asynchronicity Comparison • BroadcastChannel val channel = BroadcastChannel(2) val observer1Job = launch { channel.openSubscription().use { channel -> for (value in channel) { consumeValue(value) } // subscription will be closed } }

Slide 52

Slide 52 text

Asynchronicity Comparison • BroadcastChannel val observer2Job = launch { channel.consumeEach { value -> consumeValue(value) } }

Slide 53

Slide 53 text

Asynchronicity Comparison Produce = Coroutine + Channel

Slide 54

Slide 54 text

Asynchronicity Comparison • Produce val publisher = produce(capacity = 2) { for (i in 1..5) send(i) } • Only the code inside produce can send elements to the channel

Slide 55

Slide 55 text

Asynchronicity Comparison Produce is a pretty useful way to create custom operators

Slide 56

Slide 56 text

Asynchronicity Comparison • Actor An actor also creates a coroutine with a channel built in. Produce implements the ReceiveChannel interface whereas actor implements the SendChannel interface.

Slide 57

Slide 57 text

Asynchronicity Comparison • Actor val actor = actor(CommonPool) { for (int in channel) { // iterate over received Integers } } launch { actor.send(2) }

Slide 58

Slide 58 text

Asynchronicity Comparison

Slide 59

Slide 59 text

Flow Flow is a Stream of Data, which can emit multiple values and complete with or without an error

Slide 60

Slide 60 text

Flow val myFlow: Flow = flow { emit(1) emit(2) delay(100) emit(3) } flow.collect { value -> println("Received $value") } Received 1 Received 2 Received 3 $$==>

Slide 61

Slide 61 text

Flow + Channels • Channels are Hot whereas Flows are cold • One Emits values from 1 side, receives from other side • A bit complex to work with

Slide 62

Slide 62 text

Flow + Channels • Hot + Cold = Hot • Ex: Flow from a channel

Slide 63

Slide 63 text

Flow + Channels object Action { private val channel = BroadcastChannel(1) button.onClick { channel.send(Unit) } fun getFlow(): Flow = channel.asFlow() } Action.getFlow().collect { println(“Received Emission”) }

Slide 64

Slide 64 text

Flow + Channels object Action { private val channel = BroadcastChannel(1) button.onClick { channel.send(Unit) } fun getFlow(): Flow = channel.asFlow() } Action.getFlow().collect { println(“Received Emission”) }

Slide 65

Slide 65 text

Flow + Channels object Action { private val channel = BroadcastChannel(1) button.onClick { channel.send(Unit) } fun getFlow(): Flow = channel.asFlow() } Action.getFlow().collect { println(“Received Emission”) }

Slide 66

Slide 66 text

Flow + Channels object Action { private val channel = BroadcastChannel(1) button.onClick { channel.send(Unit) } fun getFlow(): Flow = channel.asFlow() } Action.getFlow().collect { println(“Received Emission”) }

Slide 67

Slide 67 text

Flow and Rx compositeDisposable.add(api.search(query, sort) .map{response -> response.toList() } .flatMap { searchResults -> cache.saveAndReturn(searchResults) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( { searchResults -> displayResults(searchResults) }, { error -> Log.e("TAG", "Failed to search", error) } ))

Slide 68

Slide 68 text

Flow and Rx try { flow { emit(api.search(query, sort))} .map{response ->response.toList()} .flatMapConcat { searchResults -> cache.saveAndReturn(searchResults) } .flowOn(Dispatchers.IO) .collect { searchResultList -> withContext(Dispatchers.Main) { showSearchResult(searchResultList) } } } catch(e: Exception) { e.printStackTrace() }

Slide 69

Slide 69 text

Rx / Suspend //Rx Observable.interval(1, TimeUnit.SECONDS) .subscribe { textView.text = "$it seconds have passed" } //Coroutine GlobalScope.launch { var i = 0 while (true){ textView.text = "${it++} seconds have passed" delay(1000) } }

Slide 70

Slide 70 text

Rx / Suspend publishSubject .debounce(300, TimeUnit.MILLISECONDS) .distinctUntilChanged() .switchMap { searchQuery -> api.fetchResults(query, sort) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ ... })

Slide 71

Slide 71 text

Rx / Suspend launch { broadcast.consumeEach { delay(300) val results = api.fetchResults(it) .await() displayResultsLogic() ) } }

Slide 72

Slide 72 text

Rx / Suspend //Rx searchQuery.addTextChangedListener(object: TextWatcher { override fun afterTextChanged(s: Editable?) { publishSubject.onNext(s.toString()) } } //Coroutines searchQuery.addTextChangedListener(object: TextWatcher { override fun afterTextChanged(s: Editable?) { broadcast.send(s.toString()) } }

Slide 73

Slide 73 text

bit.ly/droidcon_in_async Thats all folks! @aditlal Adit Lal adit.dev @rivuchakraborty Rivu chakraborty rivu.dev