Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Going with the Flow: Coroutines 1.3.x

Rick Busarow
November 24, 2019

Going with the Flow: Coroutines 1.3.x

Let’s talk reactive coroutines. The coroutines 1.3.0 release graduated Flow to stable, and introduced breaking changes to the Channel api.

When do we use Channels, and when do we use Flows? How do we maintain structured concurrency in our code, and how do we test it? Do we need to re-write our apps into MVFlow?

In this talk, we’ll evaluate the options for reactive programming with coroutines. We’ll go over Android-specific problems and how to fit their solutions into any existing architecture.

Rick Busarow

November 24, 2019
Tweet

More Decks by Rick Busarow

Other Decks in Programming

Transcript

  1. GOING WITH THE FLOW Droidcon San Francisco Nov 26, 2019

    rbusarow Rick Busarow COROUTINES 1.3.X
  2. @rbusarow fun doSomethingSlow() { api.getAllData() .enqueue { ... } }

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

    interface Api { @GET(“/all") fun getAllData(): Call<List<Data>> } Suspend asynchronous
  4. @rbusarow Suspend domain logic IO library request + callback callbacks

    callback callback callback(response) resource request response
  5. @rbusarow fun doSomethingSlow() { api.getAllData() .enqueue { ... } }

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

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

    suspend fun getAllData(): List<Data> } Suspend suspend function ‘getAllData’ should be called only from a coroutine or another suspend function
  8. @rbusarow suspend fun doSomethingSlow() { api.getAllData() } interface Api {

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

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

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

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

    @GET(“/all") fun getAllData(continuation: Continuation<List<Data>>) } interface Continuation<in T> { val context: CoroutineContext fun resumeWith(result: Result<T>) } Suspend
  13. @rbusarow fun doSomethingSlow(continuation: Continuation<Unit>) { api.getAllData(object : Continuation<List<Data>> { override

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

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

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

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

    val context = continuation.context override fun resumeWith(result: Result<List<Data>>) { continuation.resumeWith(Result.success(Unit)) } }) } interface Api { @GET(“/all") fun getAllData(continuation: Continuation<List<Data>>) } Suspend
  18. Coroutine A sender Coroutine B receiver Channels Solution for shared

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

    mutable state Suspending queue Bridges the gap between different coroutines @rbusarow
  20. @rbusarow interface Channel<E> : SendChannel<E>, ReceiveChannel<E> interface SendChannel<in E> {

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

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

    suspend fun send(element: E) fun offer(element: E): Boolean } interface ReceiveChannel<out E> { suspend fun receive(): E fun poll(): E? operator fun iterator(): ChannelIterator<E> } Channels
  23. @rbusarow suspend fun main() { val channel = Channel<Int>() repeat(3)

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

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

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

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

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

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

    Channel<Int>() 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:
  30. @rbusarow fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel<Location> { val channel

    = Channel<Location>() 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
  31. @rbusarow fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel<Location> { val channel

    = Channel<Location>() 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
  32. @rbusarow fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel<Location> { val channel

    = Channel<Location>() 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
  33. @rbusarow fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel<Location> { val channel

    = Channel<Location>() 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
  34. @rbusarow fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel<Location> { val channel

    = Channel<Location>() 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
  35. @rbusarow fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel<Location> { val channel

    = Channel<Location>() 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
  36. BroadcastChannels Send data to multiple coroutines via .openSubscription() The buffer

    is on the BroadcastChannel side Shared between all consumers @rbusarow
  37. @rbusarow suspend fun main() = coroutineScope { val channel =

    Channel<Int>() launch { for (i in channel) { println("received $i") } } repeat(3) { println("sent $it”) channel.send(it) } channel.cancel() } BroadcastChannels
  38. suspend fun main() = coroutineScope { val broadcastChannel = BroadcastChannel<Int>(capacity

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

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

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

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

    = 1) val b: ReceiveChannel<Int> = broadcastChannel.openSubscription() val c: ReceiveChannel<Int> = broadcastChannel.openSubscription() launch { b.receive() } launch { for (i in c) { ... } } repeat(3) { broadcastChannel.send(it) } } @rbusarow BroadcastChannels
  43. 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
  44. 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) } }
  45. 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
  46. 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
  47. 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) } }
  48. 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
  49. 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
  50. 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
  51. 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
  52. 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
  53. 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) } }
  54. 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
  55. 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
  56. 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
  57. 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
  58. 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
  59. 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) } }
  60. @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
  61. Job @rbusarow Job hierarchy Job Job Job Job Job Job

    Job Job Job Job .cancel() Job Job Job
  62. @rbusarow suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit

    ) = coroutineScope { val done = CompletableDeferred<Unit>() 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
  63. suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit )

    = coroutineScope { val done = CompletableDeferred<Unit>() 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
  64. suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit )

    = coroutineScope { val done = CompletableDeferred<Unit>() 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
  65. suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit )

    = coroutineScope { val done = CompletableDeferred<Unit>() 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
  66. suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit )

    = coroutineScope { val done = CompletableDeferred<Unit>() 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
  67. suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit )

    = coroutineScope { val done = CompletableDeferred<Unit>() 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
  68. suspend fun observeLocation( request: LocationRequest, observer: (LocationResult) -> Unit )

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

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

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

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

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

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

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

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

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

    suspend fun <T> listen(listener: (T) -> Unit) suspend fun <T> Listenable<T>.listen(listener: (T) -> Unit) suspend fun <T> Listenable<T>.listen(listener: suspend (T) -> Unit) suspend inline fun <T> Listenable<T>.listen(listener: (T) -> Unit) suspend fun onNewListenable(listenable: Listenable<Int>) { listenable.listen { delay(1000) println("I'm listening to $it in a coroutine”) } } @rbusarow Listenable<T>
  78. Flow somewhat stable as of coroutines 1.3.0 some operators and

    builders are still in preview Channel-based functionality is still experimental @rbusarow
  79. @rbusarow class SomeClass { val lastName = findLastName() val legalName

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

    val lastName = findLastName() val legalName by lazy { "Tramar Lacel Dillard" } private fun findLastName() = "Rida" } Flow
  81. @rbusarow suspend fun main() { val letters = flowOf("a", "b",

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

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

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

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

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

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

    "c") val numbers = flow<Int> { 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:
  88. @rbusarow suspend fun main() { val letters = flowOf("a", "b",

    "c") val numbers = flow<Int> { 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
  89. @rbusarow suspend fun main() { val letters = flowOf("a", "b",

    "c") val numbers = flow<Int> { 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:
  90. @rbusarow suspend fun main() { val letters = flowOf("a", "b",

    "c") val numbers = flow<Int> { 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:
  91. @rbusarow suspend fun main() { val letters = flowOf("a", "b",

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

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

    "c") val numbers = flow<Int> { 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:
  94. @rbusarow suspend fun main() { val letters = flowOf("a", "b",

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

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

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

    "c") val numbers = flow<Int> { 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
  98. @rbusarow suspend fun main() { val letters = flowOf("a", "b",

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

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

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

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

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

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

    “c”) val numbers = flowOf(1, 2, 3) val pairs: Flow<Flow<Any>> = listOf(letters, numbers).asFlow() val events: Flow<Any> = 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:
  105. @rbusarow class SomeViewModel { private val _channel = ConflatedBroadcastChannel(1) val

    flow: Flow<Int> get() = _channel.asFlow() init { _channel.sendBlocking(2) } } ConflatedBroadcastChannel usage Send from anywhere
  106. @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
  107. @rbusarow priority = LocationRequest.PRIORITY_HIGH_ACCURACY } val locations: Flow<Location> = 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
  108. @rbusarow priority = LocationRequest.PRIORITY_HIGH_ACCURACY } val locations: Flow<Location> = 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
  109. @rbusarow priority = LocationRequest.PRIORITY_HIGH_ACCURACY } val locations: Flow<Location> = 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
  110. @rbusarow priority = LocationRequest.PRIORITY_HIGH_ACCURACY } val locations: Flow<Location> = 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
  111. @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