Slide 1

Slide 1 text

rbusarow COROUTINE-FIRST ANDROID ARCHITECTURE Rick Busarow Photo by Flipboard on Unsplash

Slide 2

Slide 2 text

ARCHITECTURE

Slide 3

Slide 3 text

ARCHITECTURE COMPONENTS

Slide 4

Slide 4 text

LiveData

Slide 5

Slide 5 text

class SomeViewModel(private val repository: SomeRepository) { private val _liveData = MutableLiveData() val liveData: LiveData = _liveData }

Slide 6

Slide 6 text

class SomeViewModel(private val repository: SomeRepository) { private val _liveData = MutableLiveData().apply { value = repository.getTheWindsOfWinter() } val liveData: LiveData = _liveData }

Slide 7

Slide 7 text

class SomeScreen : LifecycleOwner { val viewModel: SomeViewModel by lazy { … } fun onCreate() { viewModel.liveData.observe(this, Observer { str !-> tvSomeTextView.text = str }) } }

Slide 8

Slide 8 text

class SomeRepository(val api: Api) { fun getTheWindsOfWinter(): String { Thread.sleep(LONG_TIME) return api.getTwow() } }

Slide 9

Slide 9 text

class SomeRepository(val api: Api) { fun getTheWindsOfWinter(): LiveData { return MutableLiveData().apply { Thread.sleep(LONG_TIME) value = api.getTwow() } } }

Slide 10

Slide 10 text

class SomeViewModel(private val repository: SomeRepository) { private val _liveData = MutableLiveData().apply { value = repository.getTheWindsOfWinter() } val liveData: LiveData = _liveData }

Slide 11

Slide 11 text

class SomeViewModel(private val repository: SomeRepository) { val liveData: LiveData = repository.getTheWindsOfWinter() }

Slide 12

Slide 12 text

class SomeRepository(val api: Api) { fun getTheWindsOfWinter(): LiveData { return MutableLiveData().apply { Thread.sleep(LONG_TIME) value = api.getTwow() } } }

Slide 13

Slide 13 text

class SomeRepository(val api: Api) { fun getTheWindsOfWinter(): LiveData { return MutableLiveData().apply { api.getTwow { value = it } } } }

Slide 14

Slide 14 text

class SomeRepository(val api: Api) { fun getTheWindsOfWinter(): LiveData { return MutableLiveData().apply { api.getTwow { value = it } } } }

Slide 15

Slide 15 text

class SomeRepository(val api: Api) { fun getTheWindsOfWinter(): LiveData { return MutableLiveData().apply { api.getTwow { postValue(it) } } } }

Slide 16

Slide 16 text

class SomeViewModel(private val repository: SomeRepository) { val liveData: LiveData = repository.getTheWindsOfWinter() }

Slide 17

Slide 17 text

class SomeViewModel(private val repository: SomeRepository) { val liveData: LiveData> = Transformations.map(repository.getTheWindsOfWinter()) { novel !-> novel!!!.split("half a hundred") } }

Slide 18

Slide 18 text

class SomeViewModel(private val repository: SomeRepository) { val liveData: LiveData> = Transformations.map(repository.getTheWindsOfWinter()) { novel !-> novel!!!.split("half a hundred") } }

Slide 19

Slide 19 text

class SomeViewModel( private val repository: SomeRepository, private val coroutineScope: CoroutineScope ) { private val _liveData = MutableLiveData>() val liveData: LiveData> = _liveData val transform: LiveData = Transformations.map(repository.getTheWindsOfWinter()) { novel !-> coroutineScope.launch { _liveData.postValue(novel!!!.split("half a hundred”)) } } }

Slide 20

Slide 20 text

class SomeViewModel( private val repository: SomeRepository, private val coroutineScope: CoroutineScope ) { private val _liveData = MutableLiveData>() val liveData: LiveData> = _liveData val transform: LiveData = Transformations.map(repository.getTheWindsOfWinter()) { novel !-> coroutineScope.launch { _liveData.postValue(novel!!!.split("half a hundred”)) } } }

Slide 21

Slide 21 text

class SomeScreen : LifecycleOwner { val viewModel: SomeViewModel by lazy { … } fun onCreate() { viewModel.liveData.observe(this, Observer { str !-> tvSomeTextView.text = str }) } }

Slide 22

Slide 22 text

class SomeScreen : LifecycleOwner { val viewModel: SomeViewModel by lazy { … } fun onCreate() { viewModel.transform.observe(this, Observer { }) viewModel.liveData.observe(this, Observer { str !-> tvSomeTextView.text = str }) } }

Slide 23

Slide 23 text

class SomeViewModel( private val repository: SomeRepository, private val coroutineScope: CoroutineScope ) { val liveData: LiveData> = Transformations.switchMap( repository.getTheWindsOfWinter() ) { full !-> liveData(Default) { val split = full.split("half a hundred") emit(split) } } } lifecycle-livedata-ktx:2.2.0-alpha02

Slide 24

Slide 24 text

class SomeViewModel( private val repository: SomeRepository, private val coroutineScope: CoroutineScope ) { val liveData: LiveData> = Transformations.switchMap( repository.getTheWindsOfWinter() ) { full !-> liveData(Default) { val split = full.split("half a hundred") emit(split) } } } lifecycle-livedata-ktx:2.2.0-alpha02

Slide 25

Slide 25 text

class SomeViewModel( private val repository: SomeRepository, private val coroutineScope: CoroutineScope ) { val liveData: LiveData> = Transformations.switchMap( repository.getTheWindsOfWinter() ) { full !-> liveData(Default) { val split = full.split("half a hundred") emit(split) } } } lifecycle-livedata-ktx:2.2.0-alpha02

Slide 26

Slide 26 text

class SomeViewModel( private val repository: SomeRepository, private val coroutineScope: CoroutineScope ) { val liveData: LiveData> = repository.getTheWindsOfWinter().switchMap { full !-> liveData(Default) { val split = full.split("half a hundred") emit(split) } } } lifecycle-livedata-ktx:2.2.0-alpha02

Slide 27

Slide 27 text

LiveData

Slide 28

Slide 28 text

LiveData ‣ Always tied to the main thread ‣ Requires LifecycleOwner ‣ Observer-heavy ‣ Written by George R. R. Martin

Slide 29

Slide 29 text

Coroutines

Slide 30

Slide 30 text

CoroutineScope

Slide 31

Slide 31 text

interface CoroutineScope { val coroutineContext: CoroutineContext }

Slide 32

Slide 32 text

CoroutineScope 
 provides
 CoroutineContext

Slide 33

Slide 33 text

fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() !-> Unit ): Job { val newContext = newCoroutineContext(context) val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) coroutine.start(start, coroutine, block) return coroutine }

Slide 34

Slide 34 text

fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() !-> Unit ): Job { val newContext = newCoroutineContext(context) val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) coroutine.start(start, coroutine, block) return coroutine }

Slide 35

Slide 35 text

fun CoroutineScope.newCoroutineContext( context: CoroutineContext ): CoroutineContext { val combined = coroutineContext + context val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined return if ( combined !!!== Dispatchers.Default !&& combined[ContinuationInterceptor] !== null ) debug + Dispatchers.Default else debug }

Slide 36

Slide 36 text

fun CoroutineScope.newCoroutineContext( context: CoroutineContext ): CoroutineContext { val combined = coroutineContext + context val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined return if ( combined !!!== Dispatchers.Default !&& combined[ContinuationInterceptor] !== null ) debug + Dispatchers.Default else debug }

Slide 37

Slide 37 text

suspend builders derive CoroutineContext

Slide 38

Slide 38 text

suspend fun withContext( context: CoroutineContext, block: suspend CoroutineScope.() !-> T ): T = suspendCoroutineUninterceptedOrReturn sc@ { continuation !-> val oldContext = continuation.context … }

Slide 39

Slide 39 text

suspend fun withContext( context: CoroutineContext, block: suspend CoroutineScope.() !-> T ): T = suspendCoroutineUninterceptedOrReturn sc@ { continuation !-> val oldContext = continuation.context … }

Slide 40

Slide 40 text

CoroutineScope is just a CoroutineContext Provider

Slide 41

Slide 41 text

CoroutineScope CoroutineContext

Slide 42

Slide 42 text

CoroutineScope CoroutineContext

Slide 43

Slide 43 text

CoroutineScope CoroutineContext interface CoroutineContext { val job: Job val continuationInterceptor: ContinuationInterceptor val coroutineExceptionHandler: CoroutineExceptionHandler val coroutineName: CoroutineName }

Slide 44

Slide 44 text

CoroutineScope CoroutineContext interface CoroutineContext { val job: Job val continuationInterceptor: ContinuationInterceptor val coroutineExceptionHandler: CoroutineExceptionHandler val coroutineName: CoroutineName }

Slide 45

Slide 45 text

CoroutineScope CoroutineContext Job

Slide 46

Slide 46 text

Job Structured Concurrency

Slide 47

Slide 47 text

class SomeClass : CoroutineScope { val A = Job() override val coroutineContext = A fun someFunction() { val B = launch { val D = launch {} val E = launch {} } val C = launch { val F = launch {} } } }

Slide 48

Slide 48 text

class SomeClass : CoroutineScope { val A = Job() override val coroutineContext = A fun someFunction() { val B = launch { val D = launch {} val E = launch {} } val C = launch { val F = launch {} } } }

Slide 49

Slide 49 text

class SomeClass : CoroutineScope { val A = Job() override val coroutineContext = A fun someFunction() { val B = launch { val D = launch {} val E = launch {} } val C = launch { val F = launch {} } } }

Slide 50

Slide 50 text

A

Slide 51

Slide 51 text

A B

Slide 52

Slide 52 text

A B C D

Slide 53

Slide 53 text

A B C F E D

Slide 54

Slide 54 text

class SomeClass : CoroutineScope { val A = Job() override val coroutineContext = A fun someFunction() { val B = launch { val D = launch {} val E = launch {} } val C = launch { val F = launch {} } } }

Slide 55

Slide 55 text

A B C F E D

Slide 56

Slide 56 text

Cancellations

Slide 57

Slide 57 text

A B C F E D .cancel()

Slide 58

Slide 58 text

JOB JOB JOB JOB JOB JOB JOB .cancel()

Slide 59

Slide 59 text

A B C F E D .cancel()

Slide 60

Slide 60 text

A B C F E D .cancel()

Slide 61

Slide 61 text

Exceptions

Slide 62

Slide 62 text

A B C F E D .throw(Exception(“oops”))

Slide 63

Slide 63 text

A B C F E D .throw(Exception(“oops”))

Slide 64

Slide 64 text

SupervisorJob

Slide 65

Slide 65 text

class SomeClass : CoroutineScope { val A = Job() override val coroutineContext = A fun someFunction() { val B = launch { val D = launch {} val E = launch {} } val C = launch { val F = launch {} } } }

Slide 66

Slide 66 text

class SomeClass : CoroutineScope { val A = SupervisorJob() override val coroutineContext = A fun someFunction() { val B = launch { val D = launch {} val E = launch {} } val C = launch { val F = launch {} } } }

Slide 67

Slide 67 text

class SomeClass : CoroutineScope { val A = SupervisorJob() override val coroutineContext = A fun someFunction() { val B = launch { val D = launch {} val E = launch {} } val C = launch { val F = launch {} } } }

Slide 68

Slide 68 text

A B C F E D SupervisorJob

Slide 69

Slide 69 text

A B C F E D SupervisorJob .throw(Exception(“oops”))

Slide 70

Slide 70 text

A B C F E D SupervisorJob .throw(Exception(“oops”))

Slide 71

Slide 71 text

Don’t implement CoroutineScope

Slide 72

Slide 72 text

class FileParser(file: File) : CoroutineScope { override val coroutineContext = Job() + Dispatchers.IO suspend fun parse(): String = TODO() }

Slide 73

Slide 73 text

class FileParser(file: File) : CoroutineScope { override val coroutineContext = Job() + Dispatchers.IO suspend fun parse(): String = TODO() } class SomeOtherClass { var novel: String? = null suspend fun onUpdatedFile(file: File) { novel = FileParser(file).parse() } }

Slide 74

Slide 74 text

class FileParser(file: File) : CoroutineScope { override val coroutineContext = Job() + Dispatchers.IO suspend fun parse(): String = TODO() } class SomeOtherClass { var novel: String? = null suspend fun onUpdatedFile(file: File) { FileParser(file).launch { … } } }

Slide 75

Slide 75 text

class FileParser(file: File) { private val scope = CoroutineScope(Job() + Dispatchers.IO) suspend fun parse(): String = TODO() } class SomeOtherClass { var novel: String? = null suspend fun onUpdatedFile(file: File) { FileParser(file).launch { … } } }

Slide 76

Slide 76 text

https://bit.ly/2y2NDuJ

Slide 77

Slide 77 text

Factories

Slide 78

Slide 78 text

fun SupervisorScope( continuationInterceptor: ContinuationInterceptor = Dispatchers.Default ): CoroutineScope = CoroutineScope(SupervisorJob() + continuationInterceptor)

Slide 79

Slide 79 text

fun SupervisorScope( continuationInterceptor: ContinuationInterceptor = Dispatchers.Default ): CoroutineScope = CoroutineScope(SupervisorJob() + continuationInterceptor) fun DefaultCoroutineScope() = SupervisorScope(Dispatchers.Default) fun IOCoroutineScope() = SupervisorScope(Dispatchers.IO)

Slide 80

Slide 80 text

Coroutine LiveData?

Slide 81

Slide 81 text

LiveData !~= BroadcastChannel

Slide 82

Slide 82 text

What’s a Channel?

Slide 83

Slide 83 text

Channel ‣ Observable type ‣ Linked blocking queue ‣ Thread Concurrency-safe ‣ Synchronization primitive ‣ Sender and receiver have their own CoroutineScope

Slide 84

Slide 84 text

suspend fun talkToYourself() { val channel = Channel() scope.launch { repeat(5) { channel.send(it) } } val output = mutableListOf() for (i in channel) { output.add(i) } output shouldBe listOf(0, 1, 2, 3, 5) }

Slide 85

Slide 85 text

suspend fun talkToYourself() { val channel = Channel() scope.launch { repeat(5) { channel.send(it) } } val output = mutableListOf() for (i in channel) { output.add(i) } output shouldBe listOf(0, 1, 2, 3, 5) }

Slide 86

Slide 86 text

suspend fun talkToYourself() { val channel = Channel() scope.launch { repeat(5) { channel.send(it) } } val output = mutableListOf() for (i in channel) { output.add(i) } output shouldBe listOf(0, 1, 2, 3, 5) }

Slide 87

Slide 87 text

suspend fun talkToYourself() { val channel = Channel() scope.launch { repeat(5) { channel.send(it) } } val output = mutableListOf() for (i in channel) { output.add(i) } output shouldBe listOf(0, 1, 2, 3, 5) }

Slide 88

Slide 88 text

suspend fun talkToYourself() { val channel = Channel() scope.launch { repeat(5) { channel.send(it) } } val output = mutableListOf() for (i in channel) { output.add(i) } output shouldBe listOf(0, 1, 2, 3, 5) } ORDER OF OPERATIONS Send 0 Receive 0 Send 1 Receive 1 …

Slide 89

Slide 89 text

Buffers

Slide 90

Slide 90 text

Buffers ‣ Rendezvous ‣ LinkedList ‣ Array(size) ‣ Conflated

Slide 91

Slide 91 text

Emitters

Slide 92

Slide 92 text

Emmitters suspend fun send(t: T) fun offer(t: T): Boolean

Slide 93

Slide 93 text

BroadcastChannels

Slide 94

Slide 94 text

val channel: ReceiveChannel = BroadcastChannel(CONFLATED).openSubscription()

Slide 95

Slide 95 text

Broadcast buffers ‣ Array(size) ‣ Conflated ‣ No Rendezvous ‣ No LinkedList

Slide 96

Slide 96 text

ReceiveChannel Vs SendChannel

Slide 97

Slide 97 text

class SomeClass { private val _channel = Channel() val channel: ReceiveChannel = _channel }

Slide 98

Slide 98 text

Observing!

Slide 99

Slide 99 text

fun ReceiveChannel.observe( coroutineScope: CoroutineScope, observer: CoroutineScope.(t: T) !-> Unit ): Job = coroutineScope.launch { for (t in this@observe) { observer(t) } }

Slide 100

Slide 100 text

fun ReceiveChannel.observe( coroutineScope: CoroutineScope, observer: CoroutineScope.(t: T) !-> Unit ): Job = coroutineScope.launch { for (t in this@observe) { observer(t) } }

Slide 101

Slide 101 text

fun ReceiveChannel.observe( coroutineScope: CoroutineScope, observer: CoroutineScope.(t: T) !-> Unit ): Job = coroutineScope.launch { for (t in this@observe) { observer(t) } }

Slide 102

Slide 102 text

fun ReceiveChannel.observe( coroutineScope: CoroutineScope, observer: CoroutineScope.(t: T) !-> Unit ): Job = coroutineScope.launch { for (t in this@observe) { observer(t) } }

Slide 103

Slide 103 text

class SomeClass( val someRepository: SomeRepository, val scope: CoroutineScope ) { init { someRepository.channel.observe(scope) { emitted !-> println(emitted) } } }

Slide 104

Slide 104 text

class SomeClass( val someRepository: SomeRepository, val scope: CoroutineScope ) { init { someRepository.channel.observe(scope) { emitted !-> println(emitted) } } }

Slide 105

Slide 105 text

CoroutineScope and the Android lifecycle

Slide 106

Slide 106 text

interface Screen : LifecycleOwner, CoroutineScope

Slide 107

Slide 107 text

fun Screen.whileResumed(block: CoroutineScope.() !-> Job) { var job: Job? = null val observer = object : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun onResume() { job = block() } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun onPause() { job!?.cancel() } } lifecycle.addObserver(observer) }

Slide 108

Slide 108 text

fun Screen.whileResumed(block: CoroutineScope.() !-> Job) { var job: Job? = null val observer = object : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun onResume() { job = block() } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun onPause() { job!?.cancel() } } lifecycle.addObserver(observer) }

Slide 109

Slide 109 text

fun Screen.whileResumed(block: CoroutineScope.() !-> Job) { var job: Job? = null val observer = object : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun onResume() { job = block() } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun onPause() { job!?.cancel() } } lifecycle.addObserver(observer) }

Slide 110

Slide 110 text

fun Screen.whileResumed(block: CoroutineScope.() !-> Job) { var job: Job? = null val observer = object : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun onResume() { job = block() } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun onPause() { job!?.cancel() } } lifecycle.addObserver(observer) }

Slide 111

Slide 111 text

fun Screen.whileResumed(block: CoroutineScope.() !-> Job) { var job: Job? = null val observer = object : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun onResume() { job = block() } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun onPause() { job!?.cancel() } } lifecycle.addObserver(observer) }

Slide 112

Slide 112 text

fun Screen.whileResumed(block: CoroutineScope.() !-> Job) { var job: Job? = null val observer = object : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun onResume() { job = block() } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun onPause() { job!?.cancel() } } lifecycle.addObserver(observer) }

Slide 113

Slide 113 text

fun Screen.whileResumed(block: CoroutineScope.() !-> Job) { var job: Job? = null val observer = object : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun onResume() { job = block() } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun onPause() { job!?.cancel() } } lifecycle.addObserver(observer) }

Slide 114

Slide 114 text

Coroutining the Uncoroutine

Slide 115

Slide 115 text

fun getUserProfile(): Deferred { val deferred = CompletableDeferred() api.getUserProfile() .enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { deferred.complete(response.body()) } override fun onFailure(call: Call, t: Throwable) { deferred.complete(null) } }) return deferred }

Slide 116

Slide 116 text

fun getUserProfile(): Deferred { val deferred = CompletableDeferred() api.getUserProfile() .enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { deferred.complete(response.body()) } override fun onFailure(call: Call, t: Throwable) { deferred.complete(null) } }) return deferred }

Slide 117

Slide 117 text

fun getUserProfile(): Deferred { val deferred = CompletableDeferred() api.getUserProfile() .enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { deferred.complete(response.body()) } override fun onFailure(call: Call, t: Throwable) { deferred.complete(null) } }) return deferred }

Slide 118

Slide 118 text

fun getUserProfile(): Deferred { val deferred = CompletableDeferred() api.getUserProfile() .enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { deferred.complete(response.body()) } override fun onFailure(call: Call, t: Throwable) { deferred.complete(null) } }) return deferred }

Slide 119

Slide 119 text

fun getUserProfile(): Deferred { val deferred = CompletableDeferred() api.getUserProfile() .enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { deferred.complete(response.body()) } override fun onFailure(call: Call, t: Throwable) { deferred.complete(null) } }) return deferred }

Slide 120

Slide 120 text

class SomeClass(val repository: ProfileRepository) { var profile: UserProfile? = null suspend fun updateProfile() { profile = repository.getUserProfile().await() } }

Slide 121

Slide 121 text

class SomeClass(val repository: ProfileRepository) { var profile: UserProfile? = null suspend fun updateProfile() { profile = repository.getUserProfile().await() } }

Slide 122

Slide 122 text

rbusarow rbusarow rbusarow Thanks! Rick Busarow