Slide 1

Slide 1 text

GOING WITH THE FLOW Droidcon San Francisco Nov 26, 2019 rbusarow Rick Busarow COROUTINES 1.3.X

Slide 2

Slide 2 text

@rbusarow Slides bit.ly/GoingWithTheFlow13x

Slide 3

Slide 3 text

@rbusarow suspend

Slide 4

Slide 4 text

@rbusarow fun doSomethingSlow() { api.getAllData() .enqueue { ... } } interface Api { @GET(“/all") fun getAllData(): Call> } Suspend

Slide 5

Slide 5 text

@rbusarow fun doSomethingSlow() { api.getAllData() .enqueue { ... } } interface Api { @GET(“/all") fun getAllData(): Call> } Suspend asynchronous

Slide 6

Slide 6 text

@rbusarow Suspend

Slide 7

Slide 7 text

@rbusarow Suspend domain logic IO library resource

Slide 8

Slide 8 text

@rbusarow Suspend domain logic IO library request + callback resource

Slide 9

Slide 9 text

@rbusarow Suspend domain logic IO library request + callback callbacks callback resource

Slide 10

Slide 10 text

@rbusarow Suspend domain logic IO library request + callback callbacks callback resource request

Slide 11

Slide 11 text

@rbusarow Suspend domain logic IO library request + callback callbacks callback resource request response

Slide 12

Slide 12 text

@rbusarow Suspend domain logic IO library request + callback callbacks callback callback resource request response

Slide 13

Slide 13 text

@rbusarow Suspend domain logic IO library request + callback callbacks callback callback callback(response) resource request response

Slide 14

Slide 14 text

@rbusarow fun doSomethingSlow() { api.getAllData() .enqueue { ... } } interface Api { @GET(“/all") fun getAllData(): Call> } Suspend

Slide 15

Slide 15 text

@rbusarow fun doSomethingSlow() { api.getAllData() .enqueue { ... } } interface Api { @GET(“/all") suspend fun getAllData(): List } Suspend

Slide 16

Slide 16 text

@rbusarow fun doSomethingSlow() { api.getAllData() } interface Api { @GET(“/all”) suspend fun getAllData(): List } Suspend

Slide 17

Slide 17 text

@rbusarow fun doSomethingSlow() { api.getAllData() } interface Api { @GET(“/all”) suspend fun getAllData(): List } Suspend suspend function ‘getAllData’ should be called only from a coroutine or another suspend function

Slide 18

Slide 18 text

@rbusarow suspend fun doSomethingSlow() { api.getAllData() } interface Api { @GET(“/all") suspend fun getAllData(): List } Suspend

Slide 19

Slide 19 text

@rbusarow Suspend By end of day, sure!

Slide 20

Slide 20 text

@rbusarow suspend fun doSomethingSlow() { api.getAllData() } interface Api { @GET(“/all") suspend fun getAllData(): List } Suspend

Slide 21

Slide 21 text

@rbusarow suspend fun doSomethingSlow() { api.getAllData() } interface Api { @GET(“/all") suspend fun getAllData(): List } Suspend Hey compiler! Turn this into a callback!

Slide 22

Slide 22 text

@rbusarow suspend fun doSomethingSlow() { api.getAllData() } interface Api { @GET(“/all") suspend fun getAllData(): List } interface Continuation { val context: CoroutineContext fun resumeWith(result: Result) } Suspend

Slide 23

Slide 23 text

@rbusarow suspend fun doSomethingSlow() { api.getAllData() } interface Api { @GET(“/all") fun getAllData(continuation: Continuation>) } interface Continuation { val context: CoroutineContext fun resumeWith(result: Result) } Suspend

Slide 24

Slide 24 text

@rbusarow fun doSomethingSlow(continuation: Continuation) { api.getAllData(object : Continuation> { override val context = continuation.context override fun resumeWith(result: Result>) { continuation.resumeWith(Result.success(Unit)) } }) } interface Api { @GET(“/all") fun getAllData(continuation: Continuation>) } Suspend

Slide 25

Slide 25 text

@rbusarow fun doSomethingSlow(continuation: Continuation) { api.getAllData(object : Continuation> { override val context = continuation.context override fun resumeWith(result: Result>) { continuation.resumeWith(Result.success(Unit)) } }) } interface Api { @GET(“/all") fun getAllData(continuation: Continuation>) } Suspend

Slide 26

Slide 26 text

@rbusarow fun doSomethingSlow(continuation: Continuation) { api.getAllData(object : Continuation> { override val context = continuation.context override fun resumeWith(result: Result>) { continuation.resumeWith(Result.success(Unit)) } }) } interface Api { @GET(“/all") fun getAllData(continuation: Continuation>) } Suspend

Slide 27

Slide 27 text

@rbusarow fun doSomethingSlow(continuation: Continuation) { api.getAllData(object : Continuation> { override val context = continuation.context override fun resumeWith(result: Result>) { continuation.resumeWith(Result.success(Unit)) } }) } interface Api { @GET(“/all") fun getAllData(continuation: Continuation>) } Suspend

Slide 28

Slide 28 text

@rbusarow fun doSomethingSlow(continuation: Continuation) { api.getAllData(object : Continuation> { override val context = continuation.context override fun resumeWith(result: Result>) { continuation.resumeWith(Result.success(Unit)) } }) } interface Api { @GET(“/all") fun getAllData(continuation: Continuation>) } Suspend

Slide 29

Slide 29 text

@rbusarow Hot Channels

Slide 30

Slide 30 text

Coroutine A sender Coroutine B receiver Channels Solution for shared mutable state Suspending queue Bridges the gap between different coroutines @rbusarow

Slide 31

Slide 31 text

Coroutine A sender Coroutine B receiver Channels Solution for shared mutable state Suspending queue Bridges the gap between different coroutines @rbusarow

Slide 32

Slide 32 text

@rbusarow interface Channel : SendChannel, ReceiveChannel interface SendChannel { suspend fun send(element: E) fun offer(element: E): Boolean } interface ReceiveChannel { suspend fun receive(): E fun poll(): E? operator fun iterator(): ChannelIterator } Channels

Slide 33

Slide 33 text

@rbusarow interface Channel : SendChannel, ReceiveChannel interface SendChannel { suspend fun send(element: E) fun offer(element: E): Boolean } interface ReceiveChannel { suspend fun receive(): E fun poll(): E? operator fun iterator(): ChannelIterator } Channels

Slide 34

Slide 34 text

@rbusarow interface Channel : SendChannel, ReceiveChannel interface SendChannel { suspend fun send(element: E) fun offer(element: E): Boolean } interface ReceiveChannel { suspend fun receive(): E fun poll(): E? operator fun iterator(): ChannelIterator } Channels

Slide 35

Slide 35 text

@rbusarow suspend fun main() { val channel = Channel() } Channels

Slide 36

Slide 36 text

@rbusarow suspend fun main() { val channel = Channel() repeat(3) { channel.send(it) println("sent $it") } } Channels

Slide 37

Slide 37 text

@rbusarow suspend fun main() = coroutineScope { val channel = Channel() launch { for (i in channel) { println("received $i") } } repeat(3) { println("sent $it”) channel.send(it) } channel.cancel() } Channels

Slide 38

Slide 38 text

@rbusarow suspend fun main() = coroutineScope { val channel = Channel() launch { for (i in channel) { println("received $i") } } repeat(3) { println("sent $it”) channel.send(it) } channel.cancel() } Channels

Slide 39

Slide 39 text

@rbusarow suspend fun main() = coroutineScope { val channel = Channel() launch { for (i in channel) { println("received $i") } } repeat(3) { println("sent $it”) channel.send(it) } channel.cancel() } Channels

Slide 40

Slide 40 text

@rbusarow suspend fun main() = coroutineScope { val channel = Channel() launch { for (i in channel) { println("received $i") } } repeat(3) { println("sent $it”) channel.send(it) } channel.cancel() } Channels

Slide 41

Slide 41 text

@rbusarow suspend fun main() = coroutineScope { val channel = Channel() launch { for (i in channel) { println("received $i") } } repeat(3) { println("sent $it”) channel.send(it) } channel.cancel() } Channels output:

Slide 42

Slide 42 text

@rbusarow suspend fun main() = coroutineScope { val channel = Channel() launch { for (i in channel) { println("received $i") } } repeat(3) { println("sent $it”) channel.send(it) } channel.cancel() } Channels sent 0 received 0 sent 1 received 1 sent 2 received 2 output:

Slide 43

Slide 43 text

@rbusarow Backpresure

Slide 44

Slide 44 text

Backpressure Rendezvous Unlimited Array(size) Conflated @rbusarow

Slide 45

Slide 45 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow Backpressure

Slide 46

Slide 46 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow Backpressure

Slide 47

Slide 47 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow Backpressure

Slide 48

Slide 48 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow Backpressure

Slide 49

Slide 49 text

Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow Backpressure Coroutine A sender

Slide 50

Slide 50 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow Backpressure

Slide 51

Slide 51 text

Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow Backpressure Coroutine A sender

Slide 52

Slide 52 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow Backpressure

Slide 53

Slide 53 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow Backpressure

Slide 54

Slide 54 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow Backpressure

Slide 55

Slide 55 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow Backpressure

Slide 56

Slide 56 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow Backpressure

Slide 57

Slide 57 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow buffer = 2 Backpressure

Slide 58

Slide 58 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow buffer = 2 Backpressure

Slide 59

Slide 59 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow buffer = 2 Backpressure

Slide 60

Slide 60 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow buffer = 2 Backpressure

Slide 61

Slide 61 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow buffer = 2 Backpressure

Slide 62

Slide 62 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow buffer = 2 Backpressure

Slide 63

Slide 63 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow buffer = 2 Backpressure

Slide 64

Slide 64 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow buffer = 2 Backpressure

Slide 65

Slide 65 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow buffer = 2 Backpressure

Slide 66

Slide 66 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow buffer = 2 Backpressure

Slide 67

Slide 67 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow buffer = 2 Backpressure

Slide 68

Slide 68 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow Backpressure

Slide 69

Slide 69 text

Coroutine A sender Coroutine B receiver Rendezvous Unlimited Array(size) Conflated @rbusarow Backpressure

Slide 70

Slide 70 text

@rbusarow https://youtu.be/P7ov_r1JZ1g

Slide 71

Slide 71 text

@rbusarow fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel { val channel = Channel() val callback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult) { channel.sendBlocking(locationResult.lastLocation) } } locationClient.requestLocationUpdates( locationRequest, callback, Looper.myLooper() ) channel.invokeOnClose { locationClient.removeLocationUpdates(callback) } return channel } https://youtu.be/P7ov_r1JZ1g?t=2188

Slide 72

Slide 72 text

@rbusarow fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel { val channel = Channel() val callback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult) { channel.sendBlocking(locationResult.lastLocation) } } locationClient.requestLocationUpdates( locationRequest, callback, Looper.myLooper() ) channel.invokeOnClose { locationClient.removeLocationUpdates(callback) } return channel } https://youtu.be/P7ov_r1JZ1g?t=2188

Slide 73

Slide 73 text

@rbusarow fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel { val channel = Channel() val callback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult) { channel.sendBlocking(locationResult.lastLocation) } } locationClient.requestLocationUpdates( locationRequest, callback, Looper.myLooper() ) channel.invokeOnClose { locationClient.removeLocationUpdates(callback) } return channel } https://youtu.be/P7ov_r1JZ1g?t=2188

Slide 74

Slide 74 text

@rbusarow fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel { val channel = Channel() val callback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult) { channel.sendBlocking(locationResult.lastLocation) } } locationClient.requestLocationUpdates( locationRequest, callback, Looper.myLooper() ) channel.invokeOnClose { locationClient.removeLocationUpdates(callback) } return channel } https://youtu.be/P7ov_r1JZ1g?t=2188

Slide 75

Slide 75 text

@rbusarow fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel { val channel = Channel() val callback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult) { channel.sendBlocking(locationResult.lastLocation) } } locationClient.requestLocationUpdates( locationRequest, callback, Looper.myLooper() ) channel.invokeOnClose { locationClient.removeLocationUpdates(callback) } return channel } https://youtu.be/P7ov_r1JZ1g?t=2188

Slide 76

Slide 76 text

@rbusarow fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel { val channel = Channel() val callback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult) { channel.sendBlocking(locationResult.lastLocation) } } locationClient.requestLocationUpdates( locationRequest, callback, Looper.myLooper() ) channel.invokeOnClose { locationClient.removeLocationUpdates(callback) } return channel } https://youtu.be/P7ov_r1JZ1g?t=2188

Slide 77

Slide 77 text

@rbusarow BroadcastChannels

Slide 78

Slide 78 text

BroadcastChannels Send data to multiple coroutines via .openSubscription() The buffer is on the BroadcastChannel side Shared between all consumers @rbusarow

Slide 79

Slide 79 text

@rbusarow suspend fun main() = coroutineScope { val channel = Channel() launch { for (i in channel) { println("received $i") } } repeat(3) { println("sent $it”) channel.send(it) } channel.cancel() } BroadcastChannels

Slide 80

Slide 80 text

suspend fun main() = coroutineScope { val broadcastChannel = BroadcastChannel(capacity = 1) val b: ReceiveChannel = broadcastChannel.openSubscription() val c: ReceiveChannel = broadcastChannel.openSubscription() launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } } @rbusarow BroadcastChannels

Slide 81

Slide 81 text

suspend fun main() = coroutineScope { val broadcastChannel = BroadcastChannel(capacity = 1) val b: ReceiveChannel = broadcastChannel.openSubscription() val c: ReceiveChannel = broadcastChannel.openSubscription() launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } } @rbusarow BroadcastChannels

Slide 82

Slide 82 text

suspend fun main() = coroutineScope { val broadcastChannel = BroadcastChannel(capacity = 1) val b: ReceiveChannel = broadcastChannel.openSubscription() val c: ReceiveChannel = broadcastChannel.openSubscription() launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } } @rbusarow BroadcastChannels

Slide 83

Slide 83 text

suspend fun main() = coroutineScope { val broadcastChannel = BroadcastChannel(capacity = 1) val b: ReceiveChannel = broadcastChannel.openSubscription() val c: ReceiveChannel = broadcastChannel.openSubscription() launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } } @rbusarow BroadcastChannels

Slide 84

Slide 84 text

suspend fun main() = coroutineScope { val broadcastChannel = BroadcastChannel(capacity = 1) val b: ReceiveChannel = broadcastChannel.openSubscription() val c: ReceiveChannel = broadcastChannel.openSubscription() launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } } @rbusarow BroadcastChannels

Slide 85

Slide 85 text

Coroutine C Coroutine B receiver Coroutine A sender 0 @rbusarow BroadcastChannels receiver suspend fun main() = coroutineScope { ... launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } } broadcast b C

Slide 86

Slide 86 text

broadcast b C Coroutine C Coroutine B receiver Coroutine A sender 0 @rbusarow BroadcastChannels receiver suspend fun main() = coroutineScope { ... launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } }

Slide 87

Slide 87 text

broadcast b C Coroutine C Coroutine B receiver Coroutine A sender 0 @rbusarow BroadcastChannels receiver suspend fun main() = coroutineScope { ... launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } } 0

Slide 88

Slide 88 text

broadcast b C Coroutine C Coroutine B receiver Coroutine A sender @rbusarow BroadcastChannels receiver suspend fun main() = coroutineScope { ... launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } } 0 0

Slide 89

Slide 89 text

broadcast b C Coroutine C Coroutine B receiver Coroutine A sender 0 0 @rbusarow BroadcastChannels receiver suspend fun main() = coroutineScope { ... launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } }

Slide 90

Slide 90 text

Coroutine C Coroutine B receiver Coroutine A sender 0 @rbusarow BroadcastChannels receiver 0 suspend fun main() = coroutineScope { ... launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } } broadcast b C

Slide 91

Slide 91 text

Coroutine C Coroutine B receiver Coroutine A sender @rbusarow BroadcastChannels receiver suspend fun main() = coroutineScope { ... launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } } broadcast b C

Slide 92

Slide 92 text

Coroutine C Coroutine B receiver Coroutine A sender @rbusarow BroadcastChannels receiver suspend fun main() = coroutineScope { ... launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } } broadcast b C

Slide 93

Slide 93 text

Coroutine C Coroutine A sender @rbusarow BroadcastChannels receiver suspend fun main() = coroutineScope { ... launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } } broadcast b C

Slide 94

Slide 94 text

Coroutine C Coroutine A sender @rbusarow BroadcastChannels receiver 1 suspend fun main() = coroutineScope { ... launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } } broadcast b C

Slide 95

Slide 95 text

broadcast b C Coroutine C Coroutine A sender @rbusarow BroadcastChannels receiver 1 suspend fun main() = coroutineScope { ... launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } }

Slide 96

Slide 96 text

broadcast b C Coroutine C Coroutine A sender @rbusarow BroadcastChannels receiver 1 suspend fun main() = coroutineScope { ... launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } } 1

Slide 97

Slide 97 text

broadcast b C Coroutine C Coroutine A sender @rbusarow BroadcastChannels receiver 1 suspend fun main() = coroutineScope { ... launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } } 1

Slide 98

Slide 98 text

broadcast b C Coroutine C Coroutine A sender @rbusarow BroadcastChannels receiver suspend fun main() = coroutineScope { ... launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } } 1 1

Slide 99

Slide 99 text

broadcast b C Coroutine C Coroutine A sender @rbusarow BroadcastChannels receiver 1 suspend fun main() = coroutineScope { ... launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } } 1

Slide 100

Slide 100 text

broadcast b C Coroutine A sender @rbusarow BroadcastChannels Coroutine C receiver suspend fun main() = coroutineScope { ... launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } } 1

Slide 101

Slide 101 text

broadcast b C Coroutine A sender @rbusarow BroadcastChannels 1 Coroutine C receiver 2 suspend fun main() = coroutineScope { ... launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } }

Slide 102

Slide 102 text

@rbusarow ConflatedBroadcastChannel ≈ MutableLiveData

Slide 103

Slide 103 text

@rbusarow ConflatedBroadcastChannel MutableLiveData Current property .value .value Emit lossless .send(value) .sendBlocking(value) .offer(value) .setValue(value)
 (main thread only) Emit lossy - - - .postValue(value) Multiple observers? yes Yes Backpressure? None None

Slide 104

Slide 104 text

@rbusarow But Structured Concurrency?

Slide 105

Slide 105 text

@rbusarow Job hierarchy Job

Slide 106

Slide 106 text

@rbusarow Job hierarchy Job Job Job Job Job Job Job Job Job Job Job

Slide 107

Slide 107 text

Job @rbusarow Job hierarchy Job Job Job Job Job Job Job Job Job Job .cancel() Job Job Job

Slide 108

Slide 108 text

Job @rbusarow Job hierarchy Job Job Job Job Job Job Job Job Job Job Job .cancel()

Slide 109

Slide 109 text

@rbusarow https://youtu.be/EOjq4OIWKqM Chris Banes + Adam Powell, ADS 2018

Slide 110

Slide 110 text

@rbusarow suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit ) = coroutineScope { val done = CompletableDeferred() val callback = object : LocationCallback() { val mutex = Mutex(false) var observeJob: Job? = null override fun onLocationResult(result: LocationResult) { observeJob?.cancel() observeJob = launch { mutex.withLock { observer(result) } } } } try { locationClient.requestLocationUpdates(request, callback, Looper.myLooper()) done.await() } finally { locationClient.removeLocationUpdates(callback) } } https://youtu.be/EOjq4OIWKqM?t=1994

Slide 111

Slide 111 text

suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit ) = coroutineScope { val done = CompletableDeferred() val callback = object : LocationCallback() { val mutex = Mutex(false) var observeJob: Job? = null override fun onLocationResult(result: LocationResult) { observeJob?.cancel() observeJob = launch { mutex.withLock { observer(result) } } } } try { locationClient.requestLocationUpdates(request, callback, Looper.myLooper()) done.await() } finally { locationClient.removeLocationUpdates(callback) } } @rbusarow https://youtu.be/EOjq4OIWKqM?t=1994

Slide 112

Slide 112 text

suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit ) = coroutineScope { val done = CompletableDeferred() val callback = object : LocationCallback() { val mutex = Mutex(false) var observeJob: Job? = null override fun onLocationResult(result: LocationResult) { observeJob?.cancel() observeJob = launch { mutex.withLock { observer(result) } } } } try { locationClient.requestLocationUpdates(request, callback, Looper.myLooper()) done.await() } finally { locationClient.removeLocationUpdates(callback) } } @rbusarow https://youtu.be/EOjq4OIWKqM?t=1994

Slide 113

Slide 113 text

suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit ) = coroutineScope { val done = CompletableDeferred() val callback = object : LocationCallback() { val mutex = Mutex(false) var observeJob: Job? = null override fun onLocationResult(result: LocationResult) { observeJob?.cancel() observeJob = launch { mutex.withLock { observer(result) } } } } try { locationClient.requestLocationUpdates(request, callback, Looper.myLooper()) done.await() } finally { locationClient.removeLocationUpdates(callback) } } @rbusarow https://youtu.be/EOjq4OIWKqM?t=1994

Slide 114

Slide 114 text

suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit ) = coroutineScope { val done = CompletableDeferred() val callback = object : LocationCallback() { val mutex = Mutex(false) var observeJob: Job? = null override fun onLocationResult(result: LocationResult) { observeJob?.cancel() observeJob = launch { mutex.withLock { observer(result) } } } } try { locationClient.requestLocationUpdates(request, callback, Looper.myLooper()) done.await() } finally { locationClient.removeLocationUpdates(callback) } } @rbusarow https://youtu.be/EOjq4OIWKqM?t=1994

Slide 115

Slide 115 text

suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit ) = coroutineScope { val done = CompletableDeferred() val callback = object : LocationCallback() { val mutex = Mutex(false) var observeJob: Job? = null override fun onLocationResult(result: LocationResult) { observeJob?.cancel() observeJob = launch { mutex.withLock { observer(result) } } } } try { locationClient.requestLocationUpdates(request, callback, Looper.myLooper()) done.await() } finally { locationClient.removeLocationUpdates(callback) } } @rbusarow https://youtu.be/EOjq4OIWKqM?t=1994

Slide 116

Slide 116 text

suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit ) @rbusarow Iterating toward Flow

Slide 117

Slide 117 text

suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit ) @rbusarow Iterating toward Flow way too specific

Slide 118

Slide 118 text

suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit ) suspend fun listen(listener: (T) -> Unit) @rbusarow Listenable

Slide 119

Slide 119 text

suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit ) suspend fun listen(listener: (T) -> Unit) @rbusarow Listenable member function?

Slide 120

Slide 120 text

suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit ) suspend fun listen(listener: (T) -> Unit) suspend fun Listenable.listen(listener: (T) -> Unit) @rbusarow Listenable

Slide 121

Slide 121 text

suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit ) suspend fun listen(listener: (T) -> Unit) suspend fun Listenable.listen(listener: (T) -> Unit) @rbusarow Listenable no coroutine support?

Slide 122

Slide 122 text

suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit ) suspend fun listen(listener: (T) -> Unit) suspend fun Listenable.listen(listener: (T) -> Unit) @rbusarow Listenable no coroutine support?

Slide 123

Slide 123 text

suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit ) suspend fun listen(listener: (T) -> Unit) suspend fun Listenable.listen(listener: (T) -> Unit) suspend fun Listenable.listen(listener: suspend (T) -> Unit) @rbusarow Listenable

Slide 124

Slide 124 text

suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit ) suspend fun listen(listener: (T) -> Unit) suspend fun Listenable.listen(listener: (T) -> Unit) suspend fun Listenable.listen(listener: suspend (T) -> Unit) @rbusarow Listenable Guaranteed to be in a coroutine

Slide 125

Slide 125 text

suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit ) suspend fun listen(listener: (T) -> Unit) suspend fun Listenable.listen(listener: (T) -> Unit) suspend fun Listenable.listen(listener: suspend (T) -> Unit) suspend inline fun Listenable.listen(listener: (T) -> Unit) @rbusarow Listenable

Slide 126

Slide 126 text

suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit ) suspend fun listen(listener: (T) -> Unit) suspend fun Listenable.listen(listener: (T) -> Unit) suspend fun Listenable.listen(listener: suspend (T) -> Unit) suspend inline fun Listenable.listen(listener: (T) -> Unit) suspend fun onNewListenable(listenable: Listenable) { listenable.listen { delay(1000) println("I'm listening to $it in a coroutine”) } } @rbusarow Listenable

Slide 127

Slide 127 text

suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit ) suspend fun listen(listener: (T) -> Unit) suspend fun Listenable.listen(listener: (T) -> Unit) suspend fun Listenable.listen(listener: suspend (T) -> Unit) suspend inline fun Listenable.listen(listener: (T) -> Unit) suspend fun onNewListenable(listenable: Listenable) { listenable.listen { delay(1000) println("I'm listening to $it in a coroutine”) } } @rbusarow Listenable

Slide 128

Slide 128 text

@rbusarow Cold Flows

Slide 129

Slide 129 text

Flow somewhat stable as of coroutines 1.3.0 some operators and builders are still in preview Channel-based functionality is still experimental @rbusarow

Slide 130

Slide 130 text

@rbusarow class SomeClass { val lastName = findLastName() private fun findLastName() = "Rida" } Flow

Slide 131

Slide 131 text

@rbusarow class SomeClass { val lastName = findLastName() val legalName by lazy { "Tramar Lacel Dillard" } private fun findLastName() = "Rida" } Flow

Slide 132

Slide 132 text

@rbusarow class SomeClass { val firstName = { "Flo" } val lastName = findLastName() val legalName by lazy { "Tramar Lacel Dillard" } private fun findLastName() = "Rida" } Flow

Slide 133

Slide 133 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", "c") val numbers = flow { repeat(3) { emit(it) } } } Flow

Slide 134

Slide 134 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", "c") val numbers = flow { repeat(3) { emit(it) } } } Flow

Slide 135

Slide 135 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", "c") val numbers = flow { repeat(3) { emit(it) } } } Flow

Slide 136

Slide 136 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", "c") val numbers = flow { println("starting") repeat(3) { emit(it) } println("all done") } numbers.collect { number: Int -> println("collecting $number") } } Flow

Slide 137

Slide 137 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", "c") val numbers = flow { println("starting") repeat(3) { emit(it) } println("all done") } numbers.collect { number: Int -> println("collecting $number") } } Flow Flow builder Flow collector

Slide 138

Slide 138 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", "c") val numbers = flow { println("starting") repeat(3) { emit(it) } println("all done") } numbers.collect { number: Int -> println("collecting $number") } } Flow output:

Slide 139

Slide 139 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", "c") val numbers = flow { println("starting") repeat(3) { emit(it) } println("all done") } numbers.collect { number: Int -> println("collecting $number") } } Flow starting collecting 0 collecting 1 collecting 2 all done output:

Slide 140

Slide 140 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", "c") val numbers = flow { println("starting") repeat(3) { emit(it) } println("all done") } numbers.collect { number: Int -> println("collecting $number") } numbers.collect { number: Int -> println("collecting $number again") } } Flow

Slide 141

Slide 141 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", "c") val numbers = flow { println("starting") repeat(3) { emit(it) } println("all done") } numbers.collect { number: Int -> println("collecting $number") } numbers.collect { number: Int -> println("collecting $number again") } } Flow output:

Slide 142

Slide 142 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", "c") val numbers = flow { println("starting") repeat(3) { emit(it) } println("all done") } numbers.collect { number: Int -> println("collecting $number") } numbers.collect { number: Int -> println("collecting $number again") } } Flow starting collecting 0 collecting 1 collecting 2 all done starting collecting 0 again collecting 1 again collecting 2 again all done output:

Slide 143

Slide 143 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", "c") val numbers = flow { println("starting") repeat(3) { emit(it) } println("all done") } numbers.filter { it % 2 == 0 } .collect { number: Int -> println("collecting $number") } } Flow

Slide 144

Slide 144 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", "c") val numbers = flow { println("starting") repeat(3) { emit(it) } println("all done") } numbers.filter { it % 2 == 0 } .collect { number: Int -> println("collecting $number") } } Flow output:

Slide 145

Slide 145 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", "c") val numbers = flow { println("starting") repeat(3) { emit(it) } println("all done") } numbers.filter { it % 2 == 0 } .collect { number: Int -> println("collecting $number") } } Flow starting collecting 0 collecting 2 all done output:

Slide 146

Slide 146 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", "c") val numbers = flow { println("starting") repeat(3) { emit(it) } println("all done") } numbers .collect { numberString: String -> println("collecting $numberString") } } Flow

Slide 147

Slide 147 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", "c") val numbers = flow { println("starting") repeat(3) { emit(it) } println("all done") } numbers.take(2) .collect { numberString: String -> println("collecting $numberString”) } } Flow

Slide 148

Slide 148 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", "c") val numbers = flow { println("starting") repeat(3) { emit(it) } println("all done") } numbers.take(2) .collect { number: Int -> println("collecting $number”) } } Flow output: never printed

Slide 149

Slide 149 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", "c") val numbers = flow { println("starting") repeat(3) { emit(it) } println("all done") } numbers.take(2) .collect { number: Int -> println("collecting $number”) } } Flow starting collecting 0 collecting 1 output: never printed

Slide 150

Slide 150 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", “c”) val numbers = flowOf(1, 2, 3) } Flow

Slide 151

Slide 151 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", “c”) val numbers = flowOf(1, 2, 3) val pairs: Flow> = listOf(letters, numbers).asFlow() } Flow

Slide 152

Slide 152 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", “c”) val numbers = flowOf(1, 2, 3) val pairs: Flow> = listOf(letters, numbers).asFlow() val events: Flow = all.flattenMerge(2) } Flow

Slide 153

Slide 153 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", “c”) val numbers = flowOf(1, 2, 3) val pairs: Flow> = listOf(letters, numbers).asFlow() val events: Flow = all.flattenMerge(2) events.collect { when(it) { is String -> println("collecting String : $it") is Int -> println("collecting Int : $it") } } } Flow

Slide 154

Slide 154 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", “c”) val numbers = flowOf(1, 2, 3) val pairs: Flow> = listOf(letters, numbers).asFlow() val events: Flow = all.flattenMerge(2) events.collect { when(it) { is String -> println("collecting String : $it") is Int -> println("collecting Int : $it") } } } Flow buffer size

Slide 155

Slide 155 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", “c”) val numbers = flowOf(1, 2, 3) val pairs: Flow> = listOf(letters, numbers).asFlow() val events: Flow = all.flattenMerge(2) events.collect { when(it) { is String -> println("collecting String : $it") is Int -> println("collecting Int : $it") } } } Flow output:

Slide 156

Slide 156 text

@rbusarow suspend fun main() { val letters = flowOf("a", "b", “c”) val numbers = flowOf(1, 2, 3) val pairs: Flow> = listOf(letters, numbers).asFlow() val events: Flow = all.flattenMerge(2) events.collect { when(it) { is String -> println("collecting String : $it") is Int -> println("collecting Int : $it") } } } Flow collecting String : a collecting Int : 1 collecting Int : 2 collecting Int : 3 collecting String : b collecting String : c output:

Slide 157

Slide 157 text

@rbusarow Practical coroutines

Slide 158

Slide 158 text

@rbusarow …but how do I emit new values?

Slide 159

Slide 159 text

@rbusarow

Slide 160

Slide 160 text

@rbusarow class SomeViewModel { private val _channel = ConflatedBroadcastChannel(1) val flow: Flow get() = _channel.asFlow() init { _channel.sendBlocking(2) } } ConflatedBroadcastChannel usage Send from anywhere

Slide 161

Slide 161 text

@rbusarow Location Tracking v3

Slide 162

Slide 162 text

@rbusarow class SomeLocationService @Inject constructor( private val locationClient: FusedLocationProviderClient, coroutineScope: CoroutineScope ) { private val locationRequest = LocationRequest().apply { fastestInterval = 1000 interval = 1000 priority = LocationRequest.PRIORITY_HIGH_ACCURACY } ... } http://bit.ly/BroadcastFlow

Slide 163

Slide 163 text

@rbusarow priority = LocationRequest.PRIORITY_HIGH_ACCURACY } val locations: Flow = broadcastFlow(coroutineScope) { val callback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult) { sendBlockingOrNull(locationResult.lastLocation) } } locationClient.requestLocationUpdates( locationRequest, callback, Looper.myLooper() ) awaitClose { locationClient.removeLocationUpdates(callback) } } } http://bit.ly/BroadcastFlow

Slide 164

Slide 164 text

@rbusarow priority = LocationRequest.PRIORITY_HIGH_ACCURACY } val locations: Flow = broadcastFlow(coroutineScope) { val callback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult) { sendBlockingOrNull(locationResult.lastLocation) } } locationClient.requestLocationUpdates( locationRequest, callback, Looper.myLooper() ) awaitClose { locationClient.removeLocationUpdates(callback) } } } http://bit.ly/BroadcastFlow

Slide 165

Slide 165 text

@rbusarow priority = LocationRequest.PRIORITY_HIGH_ACCURACY } val locations: Flow = broadcastFlow(coroutineScope) { val callback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult) { sendBlocking(locationResult.lastLocation) } } locationClient.requestLocationUpdates( locationRequest, callback, Looper.myLooper() ) awaitClose { locationClient.removeLocationUpdates(callback) } } } http://bit.ly/BroadcastFlow

Slide 166

Slide 166 text

@rbusarow priority = LocationRequest.PRIORITY_HIGH_ACCURACY } val locations: Flow = broadcastFlow(coroutineScope) { val callback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult) { sendBlocking(locationResult.lastLocation) } } locationClient.requestLocationUpdates( locationRequest, callback, Looper.myLooper() ) awaitClose { locationClient.removeLocationUpdates(callback) } } } http://bit.ly/BroadcastFlow

Slide 167

Slide 167 text

@rbusarow https://youtu.be/B8ppnjGPAGE?t=1091

Slide 168

Slide 168 text

@rbusarow Slides: bit.ly/GoingWithTheFlow13x Jetbrains docs: https://kotlinlang.org/docs/reference/coroutines/flow.html Android Suspenders at KotlinConf: https://youtu.be/P7ov_r1JZ1g Android Suspenders at ADS ’18: https://youtu.be/EOjq4OIWKqM LiveData with Coroutines and Flow at ADS ’19: https://youtu.be/B8ppnjGPAGE rbusarow Rick Busarow