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

Flowing in the Deep - Event Streams in Kotlin

Flowing in the Deep - Event Streams in Kotlin

The Kotlin team introduced a new type called Flow which looks similar to RxJava’s Observable or Flowable. Have you ever wondered what’s the difference between Flow and RxJava? How does Flow work under the hood? How is it connected to Coroutines? How do you write your own operator? What about Kotlin Coroutine Channels? Backpressure? Coroutine Scopes?

Join this session to get an introduction into Flow followed by a deep dive into the some core aspects of Flow to ultimately feel confident using Flow inside your android app.

Hannes Dorfmann

July 02, 2019
Tweet

More Decks by Hannes Dorfmann

Other Decks in Technology

Transcript

  1. Flowing in the Deep Exploring Event Streams in Kotlin

  2. None
  3. Hannes Dorfmann sockeqwe Gabriel Ittner gabrielittner

  4. None
  5. W FL

  6. Flowing in the Deep • Introduction • Operators • Threading

    • Useful APIs
  7. fun doHttpRequest() : X { // A long running operation

    ... return X }
  8. class MyActivity : Activity() { override fun onCreate(b: Bundle) {

    doHttpRequest() } }
  9. None
  10. class MyActivity : Activity() { override fun onCreate(b: Bundle) {

    doHttpRequest() } }
  11. class MyActivity : Activity() { override fun onCreate(b: Bundle) {

    thread { doHttpRequest() } } }
  12. class MyActivity : Activity() { override fun onCreate(b: Bundle) {

    thread { val x = doHttpRequest() ??? } } }
  13. fun doHttpRequest() : X { // A long running operation

    ... return X }
  14. fun doHttpRequest( callback: (X) -> Unit ){ // A long

    running operation ... callback(X) }
  15. fun doHttpRequest( callback: (X) -> Unit ){ thread { //

    A long running operation ... callback(X) } }
  16. class MyActivity : Activity() { override fun onCreate(b: Bundle) {

    doHttpRequest { result -> // do something with the result } } }
  17. doHttpRequest { ... }

  18. doHttpRequest { doHttpRequest { ... } }

  19. doHttpRequest { doHttpRequest { doHttpRequest { ... } } }

  20. doHttpRequest { doHttpRequest { doHttpRequest { doHttpRequest { ... }

    } } }
  21. doHttpRequest { doHttpRequest { doHttpRequest { doHttpRequest { doHttpRequest {

    ... } } } } }
  22. doHttpRequest { doHttpRequest { doHttpRequest { doHttpRequest { doHttpRequest {

    doHttpRequest { ... } } } } } }
  23. doHttpRequest { doHttpRequest { doHttpRequest { doHttpRequest { doHttpRequest {

    doHttpRequest { doHttpRequest { ... } } } } } } }
  24. Callback Hell

  25. fun doHttpRequest( callback: (X) -> Unit ) { // A

    long running operation ... callback(X) }
  26. suspend fun doHttpRequest(): X { // A long running operation

    ... return X }
  27. launch { val a = doHttpRequest() val b = doHttpRequest()

    val c = doHttpRequest() val d = doHttpRequest() val e = doHttpRequest() val f = doHttpRequest() val g = doHttpRequest() }
  28. suspend fun doHttpRequest(): X { // A long running operation

    ... return X }
  29. fun doHttpRequest(continuation : Continuation) { // A long running operation

    ... }
  30. fun doHttpRequest(continuation : Continuation) { // A long running operation

    ... contination.resume(X) }
  31. fun doHttpRequest( callback: (X) -> Unit ){ // A long

    running operation ... callback(X) }
  32. fun doHttpRequest( observer: (X) -> Unit ){ // A long

    running operation ... observer(X) }
  33. Observer Pattern

  34. fun doHttpRequest( observer: (X) -> Unit ){ // A long

    running operation ... observer(X) }
  35. fun downloadVideo( observer: (progress) -> Unit ) { }

  36. fun downloadVideo( observer: (progress) -> Unit ) { // A

    long running operation ... observer(0.1) ... observer(0.2) ... observer(1.0) }
  37. suspend fun downloadVideo() : ??? { // A long running

    operation ... ??? } With Coroutines?
  38. fun downloadVideo( observer: (progress) -> Unit )

  39. interface Downloader { fun downloadVideo( observer: (progress) -> Unit )

    }
  40. interface Downloader { fun download( observer: (progress) -> Unit )

    }
  41. interface Downloader<T> { fun download( observer: (T) -> Unit )

    }
  42. interface Downloader<T> { fun download( observer: (T) -> Unit )

    } interface DownloadObserver<T>
  43. interface Downloader<T> { fun download( observer: (T) -> Unit )

    } interface DownloadObserver<T> { fun emit( value: T ) }
  44. interface Downloader<T> { fun download( o: DownloadObserver<T> ) } interface

    DownloadObserver<T> { fun emit( value: T ) }
  45. interface Downloader<T> { fun download( o: DownloadObserver<T> ) } interface

    DownloadObserver<T> { fun emit( value: T ) } interface Downloader<T> { fun download( o: DownloadObserver<T> ) } interface DownloadObserver<T> { fun emit( value: T ) }
  46. interface Downloader<T> { fun download( o: DownloadObserver<T> ) } interface

    DownloadObserver<T> { fun emit( value: T ) } interface Downloader<T> { fun collect( o: DownloadObserver<T> ) } interface DownloadObserver<T> { fun emit( value: T ) }
  47. interface Downloader<T> { fun download( o: DownloadObserver<T> ) } interface

    DownloadObserver<T> { fun emit( value: T ) } interface Downloader<T> { fun collect( c: Collector<T> ) } interface Collector<T> { fun emit( value: T ) }
  48. interface Downloader<T> { fun download( o: DownloadObserver<T> ) } interface

    DownloadObserver<T> { fun emit( value: T ) } interface Flow<T> { fun collect( c: Collector<T> ) } interface Collector<T> { fun emit( value: T ) }
  49. interface Downloader<T> { fun download( o: DownloadObserver<T> ) } interface

    DownloadObserver<T> { fun emit( value: T ) } interface Flow<T> { fun collect( c: FlowCollector<T> ) } interface FlowCollector<T> { fun emit( value: T ) }
  50. interface Flow<T> { fun collect( c: FlowCollector<T> ) } interface

    FlowCollector<T> { fun emit( value: T ) } Flow
  51. interface Flow<out T> { suspend fun collect( c: FlowCollector<T> )

    } interface FlowCollector<in T> { suspend fun emit( value: T ) } Flow
  52. fun downloadVideo(): ??? { ... ??? }

  53. fun downloadVideo(): Flow<Double> { ... ??? }

  54. fun <T> flow(block: suspend FlowCollector<T>.() -> Unit): Flow<T> fun downloadVideo():

    Flow<Double> { ... ??? }
  55. fun <T> flow(block: suspend FlowCollector<T>.() -> Unit): Flow<T> fun downloadVideo():

    Flow<Double> = flow { ... ??? }
  56. fun <T> flow(block: suspend FlowCollector<T>.() -> Unit): Flow<T> fun downloadVideo():

    Flow<Double> = flow { // this: FlowCollector<Double> ... ??? }
  57. fun <T> flow(block: suspend FlowCollector<T>.() -> Unit): Flow<T> fun downloadVideo():

    Flow<Double> = flow { // this: FlowCollector<Double> ... emit(0.1) }
  58. fun <T> flow(block: suspend FlowCollector<T>.() -> Unit): Flow<T> fun downloadVideo():

    Flow<Double> = flow { // this is FlowCollector<Double> ... emit(0.1) ... emit(0.2) ... emit(1.0) }
  59. Let it Flow!

  60. flow { for (i in 0..5) { delay(100) emit(i) }

    }
  61. class MyViewModel : ViewModel() { init { viewModelScope.launch { flow

    { for (i in 0..5) { delay(100) emit(i) } } } } }
  62. class MyViewModel : ViewModel() { init { viewModelScope.launch { flow

    { for (i in 0..5) { delay(100) emit(i) } } } } } Launch new coroutine
  63. class MyViewModel : ViewModel() { init { viewModelScope.launch { flow

    { for (i in 0..5) { delay(100) emit(i) } } } } } Launch new coroutine 2.1.0-beta01
  64. class MyViewModel : ViewModel() { init { viewModelScope.launch { flow

    { for (i in 0..5) { delay(100) emit(i) } } } } } Suspending block
 
 executed in coroutine
  65. launch { flow { for (i in 0..5) { delay(100)

    emit(i) } } }
  66. launch { flow { for (i in 0..5) { delay(100)

    emit(i) } }.collect { value -> println(value) } }
  67. launch { flow { for (i in 0..5) { delay(100)

    emit(i) } }.collect { value -> println(value) } } Observes flow
  68. launch { flow { for (i in 0..5) { delay(100)

    emit(i) } }.collect { value -> println(value) } } notify observer about value
  69. launch { flow { for (i in 0..5) { delay(100)

    emit(i) } }.collect { value -> println(value) } }
  70. What about errors?

  71. flow<Double> { throw Exception("Something went wrong") }.collect { println(it) }

  72. flow<Double> { throw Exception("Something went wrong") }.collect { println(it) }

    re-throws
  73. try { flow<Double> { throw Exception("Something went wrong") }.collect {

    println(it) } } catch (e: Exception) { // handle error }
  74. flow<Double> { emit(0.1) throw Exception("Something went wrong") }.collect { println(it)

    }
  75. flow<Double> { emit(0.1) throw Exception("Something went wrong") }.collect { println(it)

    } sealed class DownloadStatus { }
  76. flow<Double> { emit(0.1) throw Exception("Something went wrong") }.collect { println(it)

    } sealed class DownloadStatus { data class Progress(val value: Double) : DownloadStatus() object Success : DownloadStatus() data class Error(val message: String) : DownloadStatus() }
  77. flow<DownloadStatus> { emit(DownloadStatus.Progress(0.1)) try { throw IOException(“Something went wrong”)) }

    catch (e: IOException) { emit(DownloadStatus.Error(e.message)) } }.collect { println(it) } sealed class DownloadStatus { data class Progress(val value: Double) : DownloadStatus() object Success : DownloadStatus() data class Error(val message: String) : DownloadStatus() }
  78. And completion?

  79. launch { flow { // do stuff }.collect { value

    -> println(value) } }
  80. launch { flow { // do stuff }.collect { value

    -> println(value) } } Flow gets instantiated
  81. launch { flow { // do stuff }.collect { value

    -> println(value) } } collect gets called
  82. launch { flow { // do stuff }.collect { value

    -> println(value) } } coroutine suspends here
  83. launch { flow { // do stuff }.collect { value

    -> println(value) } } executed collect suspends here
  84. launch { flow { // do stuff }.collect { value

    -> println(value) } } called for emission collect suspends here
  85. launch { flow { // do stuff }.collect { value

    -> println(value) } } executed collect suspends here
  86. launch { flow { // do stuff }.collect { value

    -> println(value) } } called for emission collect suspends here
  87. launch { flow { // do stuff }.collect { value

    -> println(value) } } collect suspends here
  88. launch { flow { // do stuff }.collect { value

    -> println(value) } }
  89. launch { flow { // do stuff }.collect { value

    -> println(value) } handleCompletion() }
  90. flow { // do stuff }.onCompletion { handleCompletion() }.collect {

    value -> println(value) } new in 1.3.0-M 2
  91. Operator

  92. flow { for (i in 0..5) { delay(100) emit(i) }

    }.map { value -> value * 2 }.collect { value -> println(value) }
  93. fun <T, R> Flow<T>.map(transformer: suspend (value: T) -> R): Flow<R>

    { }
  94. fun <T, R> Flow<T>.map(transformer: suspend (value: T) -> R): Flow<R>

    { return flow { } }
  95. fun <T, R> Flow<T>.map(transformer: suspend (value: T) -> R): Flow<R>

    { return flow { // this: FlowCollector } } fun <T> flow(block: suspend FlowCollector<in T>.() -> Unit): Flow<T>
  96. fun <T, R> Flow<T>.map(transformer: suspend (value: T) -> R): Flow<R>

    { return flow { // this: FlowCollector } }
  97. fun <T, R> Flow<T>.map(transformer: suspend (value: T) -> R): Flow<R>

    { return flow { // this: FlowCollector collect { value -> } } }
  98. fun <T, R> Flow<T>.map(transformer: suspend (value: T) -> R): Flow<R>

    { return flow { // this: FlowCollector collect { value -> } } } Flow<T>.collect
  99. fun <T, R> Flow<T>.map(transformer: suspend (value: T) -> R): Flow<R>

    { return flow { // this: FlowCollector collect { value -> } } }
  100. flow { for (i in 0..5) { delay(100) emit(i) }

    }.map { value -> value * 2 }.collect { value -> println(value) }
  101. flow { for (i in 0..5) { delay(100) emit(i) }

    }.map { value -> value * 2 }.collect { value -> println(value) } fun <T, R> Flow<T>.map(…)
  102. flow { for (i in 0..5) { delay(100) emit(i) }

    }.map { value -> value * 2 }.collect { value -> println(value) }
  103. flow { for (i in 0..5) { delay(100) emit(i) }

    }.map { value -> value * 2 }.collect { value -> println(value) }
  104. fun <T, R> Flow<T>.map(transformer: suspend (value: T) -> R): Flow<R>

    { return flow { // this: FlowCollector collect { value -> } } }
  105. fun <T, R> Flow<T>.map(transformer: suspend (value: T) -> R): Flow<R>

    { return flow { // this: FlowCollector collect { value -> } } }
  106. fun <T, R> Flow<T>.map(transformer: suspend (value: T) -> R): Flow<R>

    { return flow { // this: FlowCollector collect { value -> val r = transformer(value) } } }
  107. fun <T, R> Flow<T>.map(transformer: suspend (value: T) -> R): Flow<R>

    { return flow { // this: FlowCollector collect { value -> val r = transformer(value) emit(r) } } }
  108. fun <T, R> Flow<T>.map(transformer: suspend (value: T) -> R): Flow<R>

    { return flow { // this: FlowCollector collect { value -> val r = transformer(value) emit(r) } } } FlowCollector.emit(value)
  109. flow { for (i in 0..5) { delay(100) emit(i) }

    }.map { value -> value * 2 }.collect { value println(value) }
  110. Operator

  111. Operator Observer Pattern

  112. flow { for (i in 0..5) { delay(100) emit(i) }

    }.switchMap { value -> flow { emit(“first $value") delay(500) emit(“second $value") } }.collect { value ->
 println(value) }
  113. flow { for (i in 0..5) { delay(100) emit(i) }

    }.switchMap { value -> flow { emit(“first $value") delay(500) emit(“second $value") } }.collect { value ->
 println(value) } Console output first 0 first 1 first 2 first 3 first 4 first 5
  114. fun <T, R> Flow<T>.switchMap( mapper: suspend (value: T) -> Flow<R>

    ): Flow<R> { }
  115. fun <T, R> Flow<T>.switchMap( mapper: suspend (value: T) -> Flow<R>

    ): Flow<R> { return flow { } }
  116. fun <T, R> Flow<T>.switchMap( mapper: suspend (value: T) -> Flow<R>

    ): Flow<R> { return flow { collect { outerValue -> } } }
  117. fun <T, R> Flow<T>.switchMap( mapper: suspend (value: T) -> Flow<R>

    ): Flow<R> { return flow { collect { outerValue -> } } }
  118. flow { for (i in 0..5) { delay(100) emit(i) }

    }.switchMap { value flow { emit(“first $value") delay(500) emit(“second $value") } }.collect { value ->
 println(value) }
  119. flow { for (i in 0..5) { delay(100) emit(i) }

    }.switchMap { value -> flow { emit(“first $value") delay(500) emit(“second $value") } }.collect { value 
 println(value) }
  120. fun <T, R> Flow<T>.switchMap( mapper: suspend (value: T) -> Flow<R>

    ): Flow<R> { return flow { collect { outerValue -> } } }
  121. fun <T, R> Flow<T>.switchMap( mapper: suspend (value: T) -> Flow<R>

    ): Flow<R> { return flow { collect { outerValue -> val inner : Flow<R> = mapper(outerValue) } } }
  122. fun <T, R> Flow<T>.switchMap( mapper: suspend (value: T) -> Flow<R>

    ): Flow<R> { return flow { collect { outerValue -> val inner : Flow<R> = mapper(outerValue) inner.collect { value -> } } } }
  123. fun <T, R> Flow<T>.switchMap( mapper: suspend (value: T) -> Flow<R>

    ): Flow<R> { return flow { collect { outerValue -> val inner : Flow<R> = mapper(outerValue) launch { inner.collect { value -> } } } } }
  124. fun <T, R> Flow<T>.switchMap( mapper: suspend (value: T) -> Flow<R>

    ): Flow<R> { return flow { collect { outerValue -> val inner : Flow<R> = mapper(outerValue) launch { inner.collect { value -> emit(value) } } } } }
  125. fun <T, R> Flow<T>.switchMap( mapper: suspend (value: T) -> Flow<R>

    ): Flow<R> { return flow { // this: FlowCollector collect { outerValue -> val inner : Flow<R> = mapper(outerValue) launch { inner.collect { value -> emit(value) } } } } } FlowCollector.emit(value)
  126. fun <T, R> Flow<T>.switchMap( mapper: suspend (value: T) -> Flow<R>

    ): Flow<R> { return flow { collect { outerValue -> val inner : Flow<R> = mapper(outerValue) launch { inner.collect { value -> emit(value) } } } } }
  127. fun <T, R> Flow<T>.switchMap( mapper: suspend (value: T) -> Flow<R>

    ): Flow<R> { return flow { var currentJob: Job? = null collect { outerValue -> val inner : Flow<R> = mapper(outerValue) currentJob = launch { inner.collect { value -> emit(value) } } } } }
  128. fun <T, R> Flow<T>.switchMap( mapper: suspend (value: T) -> Flow<R>

    ): Flow<R> { return flow { var currentJob: Job? = null collect { outerValue -> val inner : Flow<R> = mapper(outerValue) currentJob?.cancelAndJoin() currentJob = launch { inner.collect { value -> emit(value) } } } } }
  129. fun <T, R> Flow<T>.switchMap( mapper: suspend (value: T) -> Flow<R>

    ): Flow<R> { return flow { coroutineScope { var currentJob: Job? = null collect { outerValue -> val inner : Flow<R> = mapper(outerValue) currentJob?.cancelAndJoin() currentJob = launch { inner.collect { value -> emit(value) } } } } } }
  130. Threading

  131. interface CoroutineContext

  132. interface CoroutineContext interface Job : CoroutineContext { public fun cancel():

    Unit // a lot more… }
  133. interface CoroutineContext interface Job : CoroutineContext { public fun cancel():

    Unit // a lot more… } abstract class CoroutineDispatcher
  134. interface CoroutineContext interface Job : CoroutineContext { public fun cancel():

    Unit // a lot more… } abstract class CoroutineDispatcher Dispatchers.Main Dispatchers.Default Dispatchers.IO Dispatchers.Unconfined newSingleThreadContext()
  135. interface CoroutineContext interface Job : CoroutineContext { public fun cancel():

    Unit // a lot more… } abstract class CoroutineDispatcher Dispatchers.Main Dispatchers.Default Dispatchers.IO Dispatchers.Unconfined newSingleThreadContext() val context: CoroutineContext = Job() + Dispatchers.IO
  136. public interface CoroutineScope { public val coroutineContext: CoroutineContext }

  137. public interface CoroutineScope { public val coroutineContext: CoroutineContext } public

    inline fun CoroutineScope.cancel()
  138. public interface CoroutineScope { public val coroutineContext: CoroutineContext } public

    inline fun CoroutineScope.cancel() public fun CoroutineScope.launch(...)
  139. public interface CoroutineScope { public val coroutineContext: CoroutineContext } public

    inline fun CoroutineScope.cancel() public fun CoroutineScope.launch(...) val ViewModel.viewModelScope: CoroutineScope val Lifecycle.coroutineScope: LifecycleCoroutineScope
  140. Threading (actually)

  141. lifecycle.coroutineScope.launchWhenStarted { }

  142. lifecycle.coroutineScope.launchWhenStarted { flow<Int> { // expensive operation }.map { it

    + 1 }.map { it.toString() }.collect { println(it) } }
  143. lifecycle.coroutineScope.launchWhenStarted { flow<Int> { // expensive operation }.map { it

    + 1 }.map { it.toString() }.collect { println(it) } } main thread
  144. lifecycle.coroutineScope.launchWhenStarted { flow<Int> { // expensive operation }.map { it

    + 1 }.map { it.toString() }.collect { println(it) } } main thread
  145. lifecycle.coroutineScope.launchWhenStarted { flow<Int> { // expensive operation }.map { it

    + 1 }.map { it.toString() }.collect { println(it) } } main thread
  146. lifecycle.coroutineScope.launchWhenStarted { flow<Int> { // expensive operation }.map { it

    + 1 }.map { it.toString() }.collect { println(it) } } main thread
  147. lifecycle.coroutineScope.launchWhenStarted { flow<Int> { // expensive operation }.map { it

    + 1 }.map { it.toString() }.collect { println(it) } }
  148. lifecycle.coroutineScope.launchWhenStarted { flow<Int> { GlobalScope.launch { // expensive operation emit(progress)

    // expensive operation emit(result) } }.map { it + 1 }.map { it.toString() }.collect { println(it) } }
  149. lifecycle.coroutineScope.launchWhenStarted { flow<Int> { withContext(Dispatchers.IO) { // expensive operation emit(progress)

    // expensive operation emit(result) } }.map { it + 1 }.map { it.toString() }.collect { println(it) } }
  150. lifecycle.coroutineScope.launchWhenStarted { flow<Int> { withContext(Dispatchers.IO) { // expensive operation emit(progress)

    // expensive operation emit(result) } }.map { it + 1 }.map { it.toString() }.collect { println(it) } } IllegalStateException
  151. lifecycle.coroutineScope.launchWhenStarted { flow<Int> { withContext(Dispatchers.IO) { // expensive operation emit(progress)

    // expensive operation emit(result) } }.map { it + 1 }.map { it.toString() }.collect { println(it) } }
  152. lifecycle.coroutineScope.launchWhenStarted { channelFlow<Int> { withContext(Dispatchers.IO) { // expensive operation emit(progress)

    // expensive operation emit(result) } }.map { it + 1 }.map { it.toString() }.collect { println(it) } }
  153. lifecycle.coroutineScope.launchWhenStarted { channelFlow<Int> { // this: ProducerScope<Int> withContext(Dispatchers.IO) { //

    expensive operation emit(progress) // expensive operation emit(result) } }.map { it + 1 }.map { it.toString() }.collect { println(it) } }
  154. lifecycle.coroutineScope.launchWhenStarted { channelFlow<Int> { // this: ProducerScope<Int> withContext(Dispatchers.IO) { //

    expensive operation send(progress) // expensive operation send(result) } }.map { it + 1 }.map { it.toString() }.collect { println(it) } }
  155. lifecycle.coroutineScope.launchWhenStarted { channelFlow<Int> { // this: ProducerScope<Int> withContext(Dispatchers.IO) { //

    expensive operation send(progress) // expensive operation send(result) } }.map { it + 1 }.map { it.toString() }.collect { println(it) } } main thread
  156. lifecycle.coroutineScope.launchWhenStarted { channelFlow<Int> { // this: ProducerScope<Int> withContext(Dispatchers.IO) { //

    expensive operation send(progress) // expensive operation send(result) } }.map { it + 1 }.map { it.toString() }.collect { println(it) } } main thread IO thread
  157. lifecycle.coroutineScope.launchWhenStarted { flow<Int> { // expensive operation }.map { it

    + 1 }.map { it.toString() }.collect { println(it) } }
  158. lifecycle.coroutineScope.launchWhenStarted { flow<Int> { // expensive operation }.map { it

    + 1 } .flowOn(Dispatchers.IO) .map { it.toString() }.collect { println(it) } }
  159. lifecycle.coroutineScope.launchWhenStarted { flow<Int> { // expensive operation }.map { it

    + 1 } .flowOn(Dispatchers.IO) .map { it.toString() }.collect { println(it) } } main thread IO thread
  160. lifecycle.coroutineScope.launchWhenStarted { flow<Int> { // expensive operation }.map { it

    + 1 } .flowOn(Dispatchers.IO) .map { it.toString() }.collect { println(it) } } main thread IO thread
  161. lifecycle.coroutineScope.launchWhenStarted { flow<Int> { // expensive operation }.map { it

    + 1 } .flowOn(Dispatchers.IO) .map { it.toString() }.collect { println(it) } } main thread IO thread
  162. lifecycle.coroutineScope.launchWhenStarted { flow<Int> { // expensive operation }.map { it

    + 1 } .flowOn(Dispatchers.IO) .map { it.toString() }.collect { println(it) } }
  163. lifecycle.coroutineScope.launchWhenStarted { flow<Int> { // expensive operation } .flowOn(Dispatchers.IO) .map

    { it + 1 } .flowOn(Dispatchers.Default) .map { it.toString() }.collect { println(it) } }
  164. lifecycle.coroutineScope.launchWhenStarted { flow<Int> { // expensive operation } .flowOn(Dispatchers.IO) .map

    { it + 1 } .flowOn(Dispatchers.Default) .map { it.toString() }.collect { println(it) } } main thread IO thread Default thread
  165. lifecycle.coroutineScope.launchWhenStarted { flow<Int> { // expensive operation } .flowOn(Dispatchers.IO) .map

    { it + 1 } .flowOn(Dispatchers.Default) .map { it.toString() }.collect { println(it) } } main thread IO thread Default thread
  166. lifecycle.coroutineScope.launchWhenStarted { flow<Int> { // expensive operation } .flowOn(Dispatchers.IO) .map

    { it + 1 } .flowOn(Dispatchers.Default) .map { it.toString() }.collect { println(it) } } main thread IO thread Default thread
  167. lifecycle.coroutineScope.launchWhenStarted { flow<Int> { // expensive operation } .flowOn(Dispatchers.IO) .map

    { it + 1 } .flowOn(Dispatchers.Default) .map { it.toString() }.collect { println(it) } } main thread IO thread Default thread
  168. Threading

  169. Threading • collect lambda is always called on the initial

    CoroutineContext
  170. Threading • collect lambda is always called on the initial

    CoroutineContext • flowOn does only influence the upstream
  171. Threading • collect lambda is always called on the initial

    CoroutineContext • flowOn does only influence the upstream • nothing can change the context of downstream emissions
  172. Some useful APIs

  173. flowOf(1, 2, 3, 4)

  174. flowOf(1, 2, 3, 4) listOf(1, 2, 3, 4).asFlow()

  175. flowOf(1, 2, 3, 4) listOf(1, 2, 3, 4).asFlow() sequenceOf(1, 2,

    3, 4).asFlow()
  176. val someFunction: (() -> Result) = ...

  177. val someFunction: (() -> Result) = ... someFunction.asFlow()

  178. val someFunction: (() -> Result) = ... someFunction.asFlow() val doHttpRequest:

    (suspend () -> Result) = ...
  179. val someFunction: (() -> Result) = ... someFunction.asFlow() val doHttpRequest:

    (suspend () -> Result) = ... doHttpRequest.asFlow()
  180. val observable: Observable<Int> = Observable.just(1)

  181. val observable: Observable<Int> = Observable.just(1) val flow: Flow<Int> = observable.toFlowable().asFlow()

  182. val observable: Observable<Int> = Observable.just(1) val flow: Flow<Int> = observable.toFlowable().asFlow()

    val observable2: Observable<Int> = flow.asPublisher().toObservable()
  183. RxJava Subject / Relay flatMap() concatMap() switchMap() Flow channel.asFlow() flatMapMerge()

    flatMapConcat() switchMap()
  184. RxJava & Flow map() filter() combineLatest() take() scan() retry() debounce()

    distinctUntilChanged() zip() takeUntil() reduce()
  185. interface MyService { @GET(“{user}/books/favorite”) suspend fun getFavoriteBook(@Path(“user") userId: Int): Book

    }
  186. new in 2.6.0 interface MyService { @GET(“{user}/books/favorite”) suspend fun getFavoriteBook(@Path(“user")

    userId: Int): Book }
  187. flow { emit(userId1) emit(userId2) } interface MyService { @GET("{user}/books/favorite") suspend

    fun getFavoriteBook(@Path("user") userId: Int): Book }
  188. flow { emit(userId1) emit(userId2) }.someOperator { userId -> val book

    = myService.getFavoriteBook(userId) book } interface MyService { @GET("{user}/books/favorite") suspend fun getFavoriteBook(@Path("user") userId: Int): Book }
  189. flow { emit(userId1) emit(userId2) }.someOperator { userId -> val book

    = myService.getFavoriteBook(userId) book }.collect { book -> println(book) } interface MyService { @GET("{user}/books/favorite") suspend fun getFavoriteBook(@Path("user") userId: Int): Book }
  190. flow { emit(userId1) emit(userId2) }.map { userId -> val book

    = myService.getFavoriteBook(userId) book }.collect { book -> println(book) } interface MyService { @GET("{user}/books/favorite") suspend fun getFavoriteBook(@Path("user") userId: Int): Book }
  191. Summary • Flow: event stream • Observer Pattern • Backed

    by Coroutines
  192. sockeqwe gabrielittner Questions?