Slide 1

Slide 1 text

SUSPENDERS ANDROID @chrisbanes

Slide 2

Slide 2 text

ANDROID SUSPENDERS chris.banes.me/suspenders

Slide 3

Slide 3 text

Android’s main thread

Slide 4

Slide 4 text

Android’s main thread measure + layout draw View inflation and many more things

Slide 5

Slide 5 text

ms 60Hz 16 Android’s main thread

Slide 6

Slide 6 text

12 ms Android’s main thread 90Hz

Slide 7

Slide 7 text

ms 120Hz 8 Android’s main thread

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

ASYNC ALL THE THINGS

Slide 10

Slide 10 text

How do we do that?

Slide 11

Slide 11 text

Executors AsyncTask How do we do that?

Slide 12

Slide 12 text

Loaders Executors AsyncTask How do we do that?

Slide 13

Slide 13 text

Future Executors Loaders How do we do that?

Slide 14

Slide 14 text

Libraries Loaders Future How do we do that?

Slide 15

Slide 15 text

WorkManager Future Libraries How do we do that?

Slide 16

Slide 16 text

Coroutines Libraries Coroutines How do we do that?

Slide 17

Slide 17 text

Coroutines Libraries Coroutines So why use ?

Slide 18

Slide 18 text

What does your typical mobile app do?

Slide 19

Slide 19 text

CRUD What does your typical mobile app do?

Slide 20

Slide 20 text

C R U D reate ead pdate elete What does your typical mobile app do?

Slide 21

Slide 21 text

Sync What does your typical mobile app do?

Slide 22

Slide 22 text

Sync What does your typical mobile app do?

Slide 23

Slide 23 text

So why coroutines? Scale on limited hardware Easier development model Great for I/O tasks

Slide 24

Slide 24 text

Tivi github.com/chrisbanes/tivi

Slide 25

Slide 25 text

kotlinx-coroutines-core -android -rx2

Slide 26

Slide 26 text

-core -android -rx2 jars: 774KB

Slide 27

Slide 27 text

-core -android -rx2 jars: 774KB apk: 499KB 3260 method refs

Slide 28

Slide 28 text

-core -android -rx2 jars: 774KB apk: 499KB 3260 method refs r8: 113KB 1208 method refs

Slide 29

Slide 29 text

-core -android -rx2 jars: 774KB apk: 499KB 3260 method refs r8: 113KB 1208 method refs r8
 optimized: 99KB 834 method refs

Slide 30

Slide 30 text

-keepclassmembernames class kotlinx.** { volatile ; }

Slide 31

Slide 31 text

ANDROID SUSPENDERS Creating routines

Slide 32

Slide 32 text

override suspend fun updateShow(showId: Long) { val localResult = localShowStore.getShow(showId) val result1 = remoteSource1.getShow(showId) val result2 = remoteSource2.getShow(showId) val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }.

Slide 33

Slide 33 text

override suspend fun updateShow(showId: Long) { val localResult = localShowStore.getShow(showId) val result1 = remoteSource1.getShow(showId) val result2 = remoteSource2.getShow(showId) val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }. 1

Slide 34

Slide 34 text

override suspend fun updateShow(showId: Long) { val localResult = localShowStore.getShow(showId) val result1 = remoteSource1.getShow(showId) val result2 = remoteSource2.getShow(showId) val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }. 1 2

Slide 35

Slide 35 text

override suspend fun updateShow(showId: Long) { val localResult = localShowStore.getShow(showId) val result1 = remoteSource1.getShow(showId) val result2 = remoteSource2.getShow(showId) val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }. 1 2 3

Slide 36

Slide 36 text

override suspend fun updateShow(showId: Long) { val localResult = localShowStore.getShow(showId) val result1 = remoteSource1.getShow(showId) val result2 = remoteSource2.getShow(showId) val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }.

Slide 37

Slide 37 text

override suspend fun updateShow(showId: Long) { val localResult = localShowStore.getShow(showId) val result1 = remoteSource1.getShow(showId) val result2 = remoteSource2.getShow(showId) val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }. 1 2 3 Runs in 1.2 seconds

Slide 38

Slide 38 text

override suspend fun updateShow(showId: Long) { val localDeferred = async {
 localShowStore.getShow(showId)
 } val remoteDeferred1 = async { remoteSource1.getShow(showId) } val remoteDeferred2 = async { remoteSource2.getShow(showId) } 
 val localResult = localDeferred.await() val result1 = remoteDeferred1.await() val result2 = remoteDeferred2.await() val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }.

Slide 39

Slide 39 text

override suspend fun updateShow(showId: Long) { val localDeferred = async {
 localShowStore.getShow(showId)
 } val remoteDeferred1 = async { remoteSource1.getShow(showId) } val remoteDeferred2 = async { remoteSource2.getShow(showId) } 
 val localResult = localDeferred.await() val result1 = remoteDeferred1.await() val result2 = remoteDeferred2.await() val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }.

Slide 40

Slide 40 text

override suspend fun updateShow(showId: Long) { val localDeferred = async {
 localShowStore.getShow(showId)
 } val remoteDeferred1 = async { remoteSource1.getShow(showId) } val remoteDeferred2 = async { remoteSource2.getShow(showId) } 
 val localResult = localDeferred.await() val result1 = remoteDeferred1.await() val result2 = remoteDeferred2.await() val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }.

Slide 41

Slide 41 text

override suspend fun updateShow(showId: Long) { val localDeferred = async {
 localShowStore.getShow(showId)
 } val remoteDeferred1 = async { remoteSource1.getShow(showId) } val remoteDeferred2 = async { remoteSource2.getShow(showId) } 
 val localResult = localDeferred.await() val result1 = remoteDeferred1.await() val result2 = remoteDeferred2.await() val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }.

Slide 42

Slide 42 text

override suspend fun updateShow(showId: Long) { val localDeferred = async {
 localShowStore.getShow(showId)
 } val remoteDeferred1 = async { remoteSource1.getShow(showId) } val remoteDeferred2 = async { remoteSource2.getShow(showId) } 
 val localResult = localDeferred.await() val result1 = remoteDeferred1.await() val result2 = remoteDeferred2.await() val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }.

Slide 43

Slide 43 text

override suspend fun updateShow(showId: Long) { val localDeferred = async {
 localShowStore.getShow(showId)
 } val remoteDeferred1 = async { remoteSource1.getShow(showId) } val remoteDeferred2 = async { remoteSource2.getShow(showId) } 
 val localResult = localDeferred.await() val result1 = remoteDeferred1.await() val result2 = remoteDeferred2.await() val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }. Runs in 0.8 second

Slide 44

Slide 44 text

How do we use them on Android?

Slide 45

Slide 45 text

class ShowDetailsViewModel: ViewModel() { val showRepository: ShowRepository = // ... val state: LiveData init { refresh() } private fun refresh() { launch { showRepository.updateShow(showId) // update view state } } }

Slide 46

Slide 46 text

class ShowDetailsViewModel: ViewModel() { val showRepository: ShowRepository = // ... val state: LiveData init { refresh() } private fun refresh() { launch { showRepository.updateShow(showId) // update view state } } }

Slide 47

Slide 47 text

class ShowDetailsViewModel: ViewModel() { val showRepository: ShowRepository = // ... val state: LiveData init { refresh() } private fun refresh() { launch { showRepository.updateShow(showId) // update view state } } }

Slide 48

Slide 48 text

class ShowDetailsViewModel: ViewModel() { val showRepository: ShowRepository = // ... val state: LiveData init { refresh() } private fun refresh() { launch { showRepository.updateShow(showId) // update view state } } }

Slide 49

Slide 49 text

class ShowDetailsViewModel: ViewModel() { val showRepository: ShowRepository = // ... val state: LiveData init {• refresh() }. private fun refresh() {• launch {• showRepository.updateShow(showId) // update view state }. }. }.

Slide 50

Slide 50 text

Jobs

Slide 51

Slide 51 text

class ShowDetailsViewModel: ViewModel() { private fun refresh() { launch { showRepository.updateShow(showId) // update view state }. }. }.

Slide 52

Slide 52 text

class ShowDetailsViewModel: ViewModel() { private fun refresh() { val job = launch { showRepository.updateShow(showId) // update view state }. }. }.

Slide 53

Slide 53 text

class ShowDetailsViewModel: ViewModel() { private fun refresh() { val job = launch { showRepository.updateShow(showId) // update view state }. // can call job.cancel() }. }.

Slide 54

Slide 54 text

class ShowDetailsViewModel: ViewModel() { private var updateShowJob: Job? = null private fun refresh() { val job = launch { showRepository.updateShow(showId) // update view state }. // can call job.cancel() }. }.

Slide 55

Slide 55 text

class ShowDetailsViewModel: ViewModel() { private var updateShowJob: Job? = null private fun refresh() { updateShowJob = launch { showRepository.updateShow(showId) // update view state }. // can call job.cancel() }. }.

Slide 56

Slide 56 text

class ShowDetailsViewModel: ViewModel() { private var updateShowJob: Job? = null private fun refresh() { updateShowJob = launch { showRepository.updateShow(showId) // update view state }. }. }.

Slide 57

Slide 57 text

class ShowDetailsViewModel: ViewModel() { private var updateShowJob: Job? = null private fun refresh() { updateShowJob = launch { showRepository.updateShow(showId) // update view state }. }. override fun onCleared() { updateShowJob ?.cancel() } }.

Slide 58

Slide 58 text

class ShowDetailsViewModel: ViewModel() { private var updateShowJob: Job? = null private var updateEpisodesJob: Job? = null private fun refresh() { updateShowJob = launch { showRepository.updateShow(showId) // update view state }. updateShowJob = launch { ... } }. override fun onCleared() { updateShowJob ?.cancel() updateEpisodesJob ?.cancel() } }.

Slide 59

Slide 59 text

class ShowDetailsViewModel: ViewModel() { private var updateShowJob: Job? = null private var updateEpisodesJob: Job? = null private var updateCastJob: Job? = null private fun refresh() { updateShowJob = launch { showRepository.updateShow(showId) // update view state }. updateShowJob = launch { ... } updateCastJob = launch { ... } }. override fun onCleared() { updateShowJob ?.cancel() updateEpisodesJob ?.cancel() updateCastJob ?.cancel() } }.

Slide 60

Slide 60 text

Coroutines can have a parent-child relationship

Slide 61

Slide 61 text

Coroutines can have a parent-child relationship Expressed via Jobs (in a context)

Slide 62

Slide 62 text

class ShowDetailsViewModel: ViewModel() { val showRepository: ShowRepository = // ... val state: LiveData private val job = Job() init {• refresh() }. private fun refresh() {• launch(parent = job) {• showRepository.updateShow(showId) // update view state }. }.

Slide 63

Slide 63 text

class ShowDetailsViewModel: ViewModel() { val showRepository: ShowRepository = // ... val state: LiveData private val job = Job() init { refresh() }. private fun refresh() { launch(parent = job) { showRepository.updateShow(showId) // update view state }. }. override onCleared() {

Slide 64

Slide 64 text

private val job = Job() init { refresh() }. private fun refresh() { launch(parent = job) { showRepository.updateShow(showId) // update view state }. }. override onCleared() { job.cancel() } .. }.

Slide 65

Slide 65 text

private val job = Job() init { refresh() }. private fun refresh() { launch(parent = job) { showRepository.updateShow(showId) // update view state }. }. override onCleared() { job.cancel() } } ..

Slide 66

Slide 66 text

private val job = Job() init { refresh() }. private fun refresh() { launch(parent = job) { showRepository.updateShow(showId) // update view state }. }. override onCleared() { job.cancel() } } ..

Slide 67

Slide 67 text

private val job = Job() init { refresh() }. private fun refresh() { launch(parent = job) { showRepository.updateShow(showId) // update view state }. }. override onCleared() { job.cancel() } } .. launch is now scoped to the lifetime of the ViewModel

Slide 68

Slide 68 text

Scopes

Slide 69

Slide 69 text

private val job = Job() init { refresh() }. private fun refresh() { launch(parent = job) { showRepository.updateShow(showId) // update view state }. }. override onCleared() { super.onCleared() job.cancel() } } .. Explicit

Slide 70

Slide 70 text

launch(parent = job) { } suspend fun updateShow(showId: Long) {
 val local = async {
 localShowStore.getShow(showId)
 }}
 val remote = async { remoteSource.getShow(showId) }} val merged = mergeShow(local.await(), remote.await()) localShowStore.saveShow(merged) }.

Slide 71

Slide 71 text

launch(parent = job) { } suspend fun updateShow(showId: Long) {
 val local = async {
 localShowStore.getShow(showId)
 }}
 val remote = async { remoteSource.getShow(showId) }} val merged = mergeShow(local.await(), remote.await()) localShowStore.saveShow(merged) }. New coroutines No parent Suspended job.cancel() Cancelled

Slide 72

Slide 72 text

init { refresh() }. private fun refresh() { launch(parent = job) { showRepository.updateShow(showId) // update view state }. }. override onCleared() { Deprecated in v0.26 async too

Slide 73

Slide 73 text

New! v0.26 CoroutineScope async & launch become instance methods Allows objects to provide scope for coroutines You provide a default context

Slide 74

Slide 74 text

class ShowDetailsViewModel: ViewModel() {• val showRepository: ShowRepository = // ... val state: LiveData private val job = Job() init { refresh() }. private fun refresh() {• launch(parent = job) {• showRepository.updateShow(showId) // update view state }. }. override onCleared() {

Slide 75

Slide 75 text

class ShowDetailsViewModel: ViewModel(), CoroutineScope {• val showRepository: ShowRepository = // ... val state: LiveData private val job = Job() init { refresh() }. private fun refresh() {• launch(parent = job) {• showRepository.updateShow(showId) // update view state }. }. override onCleared() {

Slide 76

Slide 76 text

class ShowDetailsViewModel: ViewModel(), CoroutineScope {• val showRepository: ShowRepository = // ... val state: LiveData private val job = Job() override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main init { refresh() }. private fun refresh() {• launch(parent = job) {• showRepository.updateShow(showId) // update view state }.

Slide 77

Slide 77 text

override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main init { refresh() }. private fun refresh() {• launch(parent = job) {• showRepository.updateShow(showId) // update view state }. }. override onCleared() { super.onCleared() job.cancel() }. }.

Slide 78

Slide 78 text

override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main init { refresh() }. private fun refresh() {• launch {• showRepository.updateShow(showId) // update view state }. }. override onCleared() {• super.onCleared() job.cancel() }. }.

Slide 79

Slide 79 text

override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main init { refresh() }. private fun refresh() {• this.launch(context = coroutineContext) {• showRepository.updateShow(showId) // update view state }. }. override onCleared() {• super.onCleared() job.cancel() }. }.

Slide 80

Slide 80 text

open class ScopedViewModel : ViewModel(), CoroutineScope { private val job = Job() override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main override fun onCleared() { super.onCleared() job.cancel() } }

Slide 81

Slide 81 text

launch { } suspend fun updateShow(showId: Long) {}
 val local = async {}
 localShowStore.getShow(showId)
 }}
 val remote = async {} remoteSource.getShow(showId) }} val merged = mergeShow(local.await(), remote.await()) localShowStore.saveShow(merged) }.

Slide 82

Slide 82 text

launch { } suspend fun updateShow(showId: Long) = coroutineScope {}
 val local = async {}
 localShowStore.getShow(showId)
 }}
 val remote = async {} remoteSource.getShow(showId) }} val merged = mergeShow(local.await(), remote.await()) localShowStore.saveShow(merged) }.

Slide 83

Slide 83 text

launch { } suspend fun updateShow(showId: Long) = coroutineScope {}
 val local = async {}
 localShowStore.getShow(showId)
 }}
 val remote = async {} remoteSource.getShow(showId) }} val merged = mergeShow(local.await(), remote.await()) localShowStore.saveShow(merged) }. Child coroutines Suspended job.cancel() Cancelled Children are cancelled too

Slide 84

Slide 84 text

What if I’m not using ViewModels?

Slide 85

Slide 85 text

open class ScopedFragment : Fragment(), CoroutineScope { private val job = Job() override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main override fun onDestroy() { super.onDestroy() job.cancel() } }

Slide 86

Slide 86 text

// In your Activity / Fragment val observer = MyObserver() getLifecycle().addObserver(observer) class MyObserver : DefaultLifecycleObserver {{ override fun onCreate(owner: LifecycleOwner) {{ // we’re created }- override fun onDestroy(owner: LifecycleOwner) {{ // we’re destroyed }) }{

Slide 87

Slide 87 text

class MyObserver : DefaultLifecycleObserver {{ override fun onCreate(owner: LifecycleOwner) {{ // we’re created }- override fun onDestroy(owner: LifecycleOwner) {{ // we’re destroyed }) }{ // In your Activity / Fragment val observer = MyObserver() getLifecycle().addObserver(observer)

Slide 88

Slide 88 text

class LifecycleScope : DefaultLifecycleObserver {{ override fun onDestroy(owner: LifecycleOwner) {{ // we’re destroyed }) }{

Slide 89

Slide 89 text

class LifecycleScope : DefaultLifecycleObserver {{ val job = Job(){ override fun onDestroy(owner: LifecycleOwner) {{ // we’re destroyed }) }{

Slide 90

Slide 90 text

class LifecycleScope : DefaultLifecycleObserver {{ val job = Job() override fun onDestroy(owner: LifecycleOwner) { // we’re destroyed{ job.cancel() }) }{

Slide 91

Slide 91 text

class LifecycleScope : DefaultLifecycleObserver, CoroutineScope {{ val job = Job() override fun onDestroy(owner: LifecycleOwner) { // we’re destroyed{ job.cancel() }) }{

Slide 92

Slide 92 text

class LifecycleScope : DefaultLifecycleObserver, CoroutineScope {{ val job = Job() override val coroutineContext = job + Dispatchers.Main override fun onDestroy(owner: LifecycleOwner) { // we’re destroyed{ job.cancel() }) }{ class DetailsFragment : Fragment() { private val scope = LifecycleScope() init { lifecycle.addObserver(scope) }}

Slide 93

Slide 93 text

class DetailsFragment : Fragment() { private val scope = LifecycleScope() init { lifecycle.addObserver(scope) }} override fun onCreateView( ...) { // ... }} }} // we’re destroyed{ job.cancel() }) }{

Slide 94

Slide 94 text

class DetailsFragment : Fragment() { private val scope = LifecycleScope() init { lifecycle.addObserver(scope) }} override fun onCreateView( ...) { // ... }} }}

Slide 95

Slide 95 text

class DetailsFragment : Fragment() { private val scope = LifecycleScope() init { lifecycle.addObserver(scope) }} override fun onStart() { scope.launch { // something async } } override fun onCreateView( ...) { // ... }} }}

Slide 96

Slide 96 text

class DetailsFragment : Fragment() { private val scope = LifecycleScope() init { lifecycle.addObserver(scope) }} override fun onStart() { scope.launch { // something async } } override fun onCreateView( ...) { // ... }} }} Scoped to Fragment lifecycle

Slide 97

Slide 97 text

gist.louiscad.com/LifecycleCoroutines

Slide 98

Slide 98 text

Cancellation

Slide 99

Slide 99 text

Cancellation requires co-operation

Slide 100

Slide 100 text

Cancellation requires co-operation Need to check if cancelled

Slide 101

Slide 101 text

Cancellation requires co-operation Need to check if active

Slide 102

Slide 102 text

Call a suspending function Check isActive Two ways to co-operate

Slide 103

Slide 103 text

Call a suspending function Check isActive if (job != null && !job.isActive) { throw job.getCancellationException() } Two ways to co-operate yield()

Slide 104

Slide 104 text

Two ways to co-operate Call a suspending function Check isActive launch { files.forEach { file -> if (isActive) doSomethingWith(file) } }

Slide 105

Slide 105 text

Exceptions

Slide 106

Slide 106 text

launch rethrows exceptions

Slide 107

Slide 107 text

suspend fun updateShow(showId: Long) = coroutineScope {} val local = async {} localShowStore.getShow(showId) }} val remote = async {} remoteSource.getShow(showId) }} val merged = mergeShow(local.await(), remote.await()) localShowStore.saveShow(merged) }}

Slide 108

Slide 108 text

suspend fun updateShow(showId: Long) = coroutineScope {} val local = async {} localShowStore.getShow(showId) }} val trakt = async {} remoteSource.getShow(showId) }} val merged = mergeShow(local.await(), remote.await()) localShowStore.saveShow(merged) // throws CrashyMcCrashfaceException() }}

Slide 109

Slide 109 text

suspend fun updateShow(showId: Long) = coroutineScope {} val local = async {} localShowStore.getShow(showId) }} val trakt = async {} remoteSource.getShow(showId) }} val merged = mergeShow(local.await(), remote.await()) localShowStore.saveShow(merged) // throws CrashyMcCrashfaceException() }} Treated like an uncaught exception

Slide 110

Slide 110 text

async doesn’t throw until await()

Slide 111

Slide 111 text

suspend fun updateShow(showId: Long) = coroutineScope {} val local = async {} localShowStore.getShow(showId) }} val remote = async {} remoteSource.getShow(showId) }} val merged = mergeShow(local.await(), remote.await()) localShowStore.saveShow(merged) }}

Slide 112

Slide 112 text

suspend fun updateShow(showId: Long) = coroutineScope {} val local = async {} localShowStore.getShow(showId) // throws CrashyMcCrashfaceException() }} val remote = async {} remoteSource.getShow(showId) }} val merged = mergeShow(local.await(), remote.await())} localShowStore.saveShow(merged)} }}

Slide 113

Slide 113 text

suspend fun updateShow(showId: Long) = coroutineScope {} val local = async {} localShowStore.getShow(showId) // throws CrashyMcCrashfaceException() }} val remote = async {} remoteSource.getShow(showId) }} val merged = mergeShow(local.await(), remote.await())} localShowStore.saveShow(merged)} }} Thrown here

Slide 114

Slide 114 text

suspend fun updateShow(showId: Long) = coroutineScope {} val local = async {} localShowStore.getShow(showId) // throws CrashyMcCrashfaceException() }} val remote = async {} remoteSource.getShow(showId) }} try {} val merged = mergeShow(local.await(), remote.await()) localShowStore.saveShow(merged) } catch (t: Throwable) {} // Handle the exception }} }}

Slide 115

Slide 115 text

Dispatchers

Slide 116

Slide 116 text

launch {{ // fun things }}

Slide 117

Slide 117 text

launch(context = Dispatchers.Default) {{ // fun things }}

Slide 118

Slide 118 text

What’s a CoroutineDispatcher?

Slide 119

Slide 119 text

What’s a CoroutineDispatcher? The thing which runs and schedules coroutines

Slide 120

Slide 120 text

DefaultScheduler Dispatchers.Default ===

Slide 121

Slide 121 text

DefaultScheduler New! v0.30 Elastic thread executor Uses cpuCount threads (core) Now set as Dispatchers.Default in 0.30

Slide 122

Slide 122 text

New! v0.25 IO Designed for blocking I/O tasks Uses 64 <= cpuCount threads launch(IO) {{ // network things }}

Slide 123

Slide 123 text

IO New! v0.25 Shares threads IO dispatcher uses DefaultScheduler

Slide 124

Slide 124 text

New! v0.25 async { val local = withContext(IO) { fileStore.loadImage( ...) }} // No thread context switch! return processImage(local) }} Shares threads IO dispatcher uses DefaultScheduler IO

Slide 125

Slide 125 text

async { val local = withContext(IO) { fileStore.loadImage( ...) }} // No thread context switch! return processImage(local) }} Shares threads IO dispatcher uses DefaultScheduler New! v0.25 IO

Slide 126

Slide 126 text

async { val local = withContext(IO) { fileStore.loadImage(...) }} // No thread context switch! return processImage(local) }} Shares threads IO dispatcher uses DefaultScheduler New! v0.25 IO

Slide 127

Slide 127 text

ANDROID SUSPENDERS Reactivity

Slide 128

Slide 128 text

Most devs use RxJava for easy threading

Slide 129

Slide 129 text

End up mainly using Single, Maybe & Completable

Slide 130

Slide 130 text

End up mainly using Single, Maybe & Completable Only exist in RxJava RxScala RxGroovy

Slide 131

Slide 131 text

Coroutines can replace Single / Maybe / Completable

Slide 132

Slide 132 text

service.trendingShows() .scheduleOn(schedulers.io) .subscribe(::onTrendingLoaded, ::onError) interface RemoteService {} @GET("/trendingshows") fun trendingShows(): Single> }}

Slide 133

Slide 133 text

val show = withContext(dispatchers.io) {} service.trendingShows().await() }} interface RemoteService {} @GET("/trendingshows") fun trendingShows(): Single> }}

Slide 134

Slide 134 text

val show = withContext(dispatchers.io) {} service.trendingShows().await() }} interface RemoteService {} @GET("/trendingshows") fun trendingShows(): Single> }}

Slide 135

Slide 135 text

interface RemoteService {} @GET("/trendingshows") suspend fun trendingShows(): List }} val show = withContext(dispatchers.io) {} service.trendingShows().await() }}

Slide 136

Slide 136 text

interface RemoteService {} @GET("/trendingshows") suspend fun trendingShows(): List }} val show = withContext(dispatchers.io) {} service.trendingShows().await() }} Coming to Retrofit soon™

Slide 137

Slide 137 text

interface RemoteService {} @GET("/trendingshows") suspend fun trendingShows(): List }} val show = withContext(dispatchers.io) {} service.trendingShows().await() }}

Slide 138

Slide 138 text

interface RemoteService {} @GET("/trendingshows") suspend fun trendingShows(): List }} val show = withContext(dispatchers.io) {} service.trendingShows() }}

Slide 139

Slide 139 text

Channels

Slide 140

Slide 140 text

Be aware that Channels is experimental Channels

Slide 141

Slide 141 text

Channels Not a replacement for RxJava Publishers Describe an asynchronous stream of things

Slide 142

Slide 142 text

val channel = Channel() launch { // First coroutine to load images files.forEach { file -> val bitmap = loadBitmap(file) channel.send(bitmap) } // We're done with the channel channel.close() } launch { // Second coroutine to process them

Slide 143

Slide 143 text

val channel = Channel() launch { // First coroutine to load images files.forEach { file -> val bitmap = loadBitmap(file) channel.send(bitmap) } // We're done with the channel channel.close() } launch { // Second coroutine to process them

Slide 144

Slide 144 text

val channel = Channel() launch { // First coroutine to load images files.forEach { file -> val bitmap = loadBitmap(file) channel.send(bitmap) } // We're done with the channel channel.close() } launch { // Second coroutine to process them

Slide 145

Slide 145 text

val channel = Channel() launch { // First coroutine to load images files.forEach { file -> val bitmap = loadBitmap(file) channel.send(bitmap) } // We're done with the channel channel.close() } launch { // Second coroutine to process them

Slide 146

Slide 146 text

files.forEach { file -> val bitmap = loadBitmap(file) channel.send(bitmap) } // We're done with the channel channel.close() } launch { // Second coroutine to process them channel.consumeEach { bitmap -> processBitmap(bitmap) } }

Slide 147

Slide 147 text

BroadcastChannels ≈ Subjects ConflatedBroadcastChannel ≈ BehaviorSubject Channels

Slide 148

Slide 148 text

Channels support null values Channels Channels only support streams "hot"

Slide 149

Slide 149 text

Operators

Slide 150

Slide 150 text

all any associate associateBy associateByTo associateTo consume consumeEach consumeEachIndexed consumes count distinct distinctBy drop dropWhile elementAt elementAtOrElse elementAtOrNull filter filterIndexed filterIndexedTo filterNot filterNotNull filterNotNullTo filterNotTo filterTo find findLast first firstOrNull flatMap fold foldIndexed groupBy groupByTo indexOf indexOfFirst indexOfLast last l a s t I n d e x O f l a s t O r N u l l m a p m a p I n d e x e d mapIndexedNotNull mapIndexedNotNullTo mapIndexedTo mapNotNull mapNotNullTo mapTo maxBy maxWith minBy minWith none partition reduce reduceIndexed requireNoNulls single singleOrNull sumBy sumByDouble take takeWhile toChannel toCollection toList toMap toMutableList toMutableSet toSet withIndex zip Channels has 73 built-in operators

Slide 151

Slide 151 text

aggregate all amb ambWith and apply asObservable asyncAction asyncFunc averageDouble length limit longCount map mapMany materialize max maxBy merge mergeDelayError Compare that to RxJava 230ish

Slide 152

Slide 152 text

Operators are easier to write in coroutines fun Publisher.map( context: CoroutineContext, mapper: (T) -> R ) = GlobalScope.publish(context) { consumeEach { send(mapper(it)) } }

Slide 153

Slide 153 text

ANDROID SUSPENDERS Using Android APIs

Slide 154

Slide 154 text

Get last known location One shot callback

Slide 155

Slide 155 text

Get last known location FusedLocationProviderClient In Google Play Services client.getLastLocation(): Task

Slide 156

Slide 156 text

Get last known location FusedLocationProviderClient client.getLastLocation().addOnCompleteListener { task -> // where am i? val location = task.result } Location In Google Play Services

Slide 157

Slide 157 text

callback ➡ suspend

Slide 158

Slide 158 text

callback ➡ suspend suspendCoroutine { ... }

Slide 159

Slide 159 text

suspendCoroutine { ... } Given a continuation to later resume Will run the lambda and then immediately suspend How you pass the result back

Slide 160

Slide 160 text

Get last known location suspend fun getLastLocation(): Location { }} // TODO

Slide 161

Slide 161 text

Get last known location suspend fun getLastLocation(): Location { return suspendCoroutine { continuation -> }} }} // TODO

Slide 162

Slide 162 text

Get last known location suspend fun getLastLocation(): Location { return suspendCoroutine { continuation -> }} }}

Slide 163

Slide 163 text

Get last known location suspend fun getLastLocation(): Location { return suspendCoroutine { continuation -> locationClient.lastLocation.addOnCompleteListener { task -> continuation.resume(task.result) }} }} }}

Slide 164

Slide 164 text

Get last known location suspend fun getLastLocation(): Location { return suspendCoroutine { continuation -> locationClient.lastLocation.addOnCompleteListener { task -> continuation.resume(task.result) }} }} }} Resumes coroutine with result

Slide 165

Slide 165 text

Get last known location suspend fun getLastLocation(): Location { return suspendCoroutine { continuation -> locationClient.lastLocation.addOnCompleteListener { task -> if (task.isSuccessful) { continuation.resume(task.result) } else { continuation.resumeWithException(task.exception !!) } }} }} }}

Slide 166

Slide 166 text

Observe location updates Multiple value callback

Slide 167

Slide 167 text

FusedLocationProviderClient Takes a callback parameter fun requestLocationUpdates( request: LocationRequest, callback: LocationCallback, looper: Looper ): Task

Slide 168

Slide 168 text

fun requestLocationUpdates( request: LocationRequest, callback: LocationCallback, looper: Looper ): Task

Slide 169

Slide 169 text

fun requestLocationUpdates( request: LocationRequest, callback: LocationCallback, looper: Looper ): Task

Slide 170

Slide 170 text

fun requestLocationUpdates( request: LocationRequest, callback: LocationCallback, looper: Looper ): Task

Slide 171

Slide 171 text

fun requestLocationUpdates( request: LocationRequest, callback: LocationCallback, looper: Looper ): Task

Slide 172

Slide 172 text

fun removeLocationUpdates( callback: LocationCallback ): Task Remove the callback when done request: LocationRequest, callback: LocationCallback, looper: Looper ): Task

Slide 173

Slide 173 text

fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel Lets build our function...

Slide 174

Slide 174 text

fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel {} }}

Slide 175

Slide 175 text

fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel {} val channel = Channel() // TODO update channel return channel }}

Slide 176

Slide 176 text

fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel {} val channel = Channel() val callback = object : LocationCallback() { override fun onLocationResult(result: LocationResult) { channel.sendBlocking(result.getLastLocation()) }} }} // TODO request location updates return channel }}

Slide 177

Slide 177 text

fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel { val channel = Channel() val callback = object : LocationCallback() { override fun onLocationResult(result: LocationResult) { channel.sendBlocking(result.getLastLocation()) } } // TODO request location updates return channel }

Slide 178

Slide 178 text

fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel { val channel = Channel() val callback = object : LocationCallback() { override fun onLocationResult(result: LocationResult) { channel.sendBlocking(result.getLastLocation()) }} }} // TODO request location updates return channel }}

Slide 179

Slide 179 text

fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel { val channel = Channel() val callback = object : LocationCallback() { override fun onLocationResult(result: LocationResult) { channel.sendBlocking(result.getLastLocation()) }} }} return channel }}

Slide 180

Slide 180 text

fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel { val channel = Channel() val callback = object : LocationCallback() { override fun onLocationResult(result: LocationResult) { channel.sendBlocking(result.getLastLocation()) }} }} // Start location updates, sending locations to the // channel locationClient.requestLocationUpdates( locationRequest, callback, Looper.myLooper() )}

Slide 181

Slide 181 text

val channel = Channel() val callback = object : LocationCallback() { override fun onLocationResult(result: LocationResult) { channel.sendBlocking(result.getLastLocation()) }} }} // Start location updates, sending locations to the // channel locationClient.requestLocationUpdates( locationRequest, callback, Looper.myLooper() )} return channel }}

Slide 182

Slide 182 text

}} // Start location updates, sending locations to the // channel locationClient.requestLocationUpdates( locationRequest, callback, Looper.myLooper() )} // Remove the location callback when the channel // is closed channel.invokeOnClose { locationClient.removeLocationUpdates(callback) } return channel }}

Slide 183

Slide 183 text

lateinit var locationChannel: ReceiveChannel override fun onStart() { val locationRequest = LocationRequest() .setSmallestDisplacement(100f) // 100m locationChannel = observeLocation(locationRequest) launch(Main) { locationChannel.consumeEach { // We have a new location! updateUiForLocation(it) } } } override fun onStop() { // Stop observing the location and

Slide 184

Slide 184 text

lateinit var locationChannel: ReceiveChannel override fun onStart() { val locationRequest = LocationRequest() .setSmallestDisplacement(100f) // 100m locationChannel = observeLocation(locationRequest) launch(Main) { locationChannel.consumeEach { // We have a new location! updateUiForLocation(it) } } } override fun onStop() { // Stop observing the location and

Slide 185

Slide 185 text

lateinit var locationChannel: ReceiveChannel override fun onStart() { val locationRequest = LocationRequest() .setSmallestDisplacement(100f) // 100m locationChannel = observeLocation(locationRequest) launch(Main) { locationChannel.consumeEach { // We have a new location! updateUiForLocation(it) } } } override fun onStop() { // Stop observing the location and

Slide 186

Slide 186 text

lateinit var locationChannel: ReceiveChannel override fun onStart() { val locationRequest = LocationRequest() .setSmallestDisplacement(100f) // 100m locationChannel = observeLocation(locationRequest) launch(Main) { locationChannel.consumeEach { // We have a new location! updateUiForLocation(it) }} }} }} override fun onStop() { // Stop observing the location and

Slide 187

Slide 187 text

.setSmallestDisplacement(100f) // 100m locationChannel = observeLocation(locationRequest) launch(Main) { locationChannel.consumeEach { // We have a new location! updateUiForLocation(it) }} }} }} override fun onStop() { // Stop observing the location and // close the channel locationChannel.cancel() }}

Slide 188

Slide 188 text

.setSmallestDisplacement(100f) // 100m locationChannel = observeLocation(locationRequest) launch(Main) { locationChannel.consumeEach { // We have a new location! updateUiForLocation(it) }} }} }} override fun onStop() { // Stop observing the location and // close the channel locationChannel.cancel() }}

Slide 189

Slide 189 text

ANDROID SUSPENDERS What's next?

Slide 190

Slide 190 text

Codelab https:/ /codelabs.developers.google.com/codelabs/kotlin-coroutines/

Slide 191

Slide 191 text

RTFM

Slide 192

Slide 192 text

Exploring Coroutines in Kotlin Kotlin Coroutines in Practice Coroutines and Reactive Programming - friends or foes? Effectenbeurzaal Tomorrow, 13:00 Effectenbeurzaal Tomorrow, 10:15 Granbeurszaal Tomorrow, 15:15

Slide 193

Slide 193 text

Over and out… @chrisbanes