Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Asynchronous Data Streams with Kotlin Flow

Asynchronous Data Streams with Kotlin Flow

Kotlin Flow is a declarative mechanism for working with asynchronous data streams that builds on top of Kotlin coroutines and structured concurrency. Kotlin Flows are doing the same revolution in simplicity for data streams that suspending functions did for data values.

In this talk we will discuss the challenges of working with asynchronous streams and how Kotlin Flows solve them. We will study the basics of Kotlin Flow design, see their typical usage patterns, peek behind the scenes and into some of the implementation details, checkout flow performance and see how they enable writing safe, reliable, and leak-free systems. We will also discuss how they relate to and incorporate ideas from reactive extensions and reactive streams, how they are similar and different, and how they can be used together.

Roman Elizarov

December 06, 2019
Tweet

More Decks by Roman Elizarov

Other Decks in Programming

Transcript

  1. Callback hell before fun requestTokenAsync(): Promise<Token> { … } fun

    createPostAsync(token: Token, item: Item): Promise<Post> … fun processPost(post: Post) { … } fun postItem(item: Item) { requestTokenAsync() .thenCompose { token -> createPostAsync(token, item) } .thenAccept { post -> processPost(post) } }
  2. Direct style with Kotlin Corou6nes suspend fun requestToken(): Token {

    … } suspend fun createPost(token: Token, item: Item): Post { … } fun processPost(post: Post) { … } suspend fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) } Like regular code
  3. Direct style with Kotlin Coroutines suspend fun requestToken(): Token {

    … } suspend fun createPost(token: Token, item: Item): Post { … } fun processPost(post: Post) { … } suspend fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) }
  4. Asynchronous yet sequen6al suspend fun requestToken(): Token { … }

    suspend fun createPost(token: Token, item: Item): Post { … } fun processPost(post: Post) { … } suspend fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) }
  5. Asynchronous yet sequential suspend fun requestToken(): Token { … }

    suspend fun createPost(token: Token, item: Item): Post { … } fun processPost(post: Post) { … } suspend fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) }
  6. Asynchronous yet sequential suspend fun requestToken(): Token { … }

    suspend fun createPost(token: Token, item: Item): Post { … } fun processPost(post: Post) { … } suspend fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) }
  7. suspend fun foo(): List<Response> = buildList { add(compute("A")) add(compute("B")) add(compute("C"))

    } fun main() = runBlocking { val list = foo() for (x in list) println(x) }
  8. suspend fun foo(): List<Response> = buildList { add(compute("A")) add(compute("B")) add(compute("C"))

    } fun main() = runBlocking { val list = foo() for (x in list) println(x) }
  9. suspend fun foo(): List<Response> = buildList { add(compute("A")) add(compute("B")) add(compute("C"))

    } fun main() = runBlocking { val list = foo() for (x in list) println(x) }
  10. main() foo() suspend fun foo(): List<Response> = buildList { add(compute("A"))

    add(compute("B")) add(compute("C")) } fun main() = runBlocking { val list = foo() for (x in list) println(x) }
  11. main() foo() suspend fun foo(): List<Response> = buildList { add(compute("A"))

    add(compute("B")) add(compute("C")) } fun main() = runBlocking { val list = foo() for (x in list) println(x) }
  12. main() foo() A suspend fun foo(): List<Response> = buildList {

    add(compute("A")) add(compute("B")) add(compute("C")) } fun main() = runBlocking { val list = foo() for (x in list) println(x) }
  13. main() foo() suspend fun foo(): List<Response> = buildList { add(compute("A"))

    add(compute("B")) add(compute("C")) } fun main() = runBlocking { val list = foo() for (x in list) println(x) } B A
  14. main() foo() suspend fun foo(): List<Response> = buildList { add(compute("A"))

    add(compute("B")) add(compute("C")) } fun main() = runBlocking { val list = foo() for (x in list) println(x) } C B A
  15. main() foo() suspend fun foo(): List<Response> = buildList { add(compute("A"))

    add(compute("B")) add(compute("C")) } fun main() = runBlocking { val list = foo() for (x in list) println(x) } List<Response> C B A
  16. main() foo() suspend fun foo(): List<Response> = buildList { add(compute("A"))

    add(compute("B")) add(compute("C")) } fun main() = runBlocking { val list = foo() for (x in list) println(x) } List<Response> C B A
  17. main() foo() List<Response> A B C B C A suspend

    fun foo(): List<Response> = buildList { add(compute("A")) add(compute("B")) add(compute("C")) } fun main() = runBlocking { val list = foo() for (x in list) println(x) }
  18. main() foo() List<Response> A B C B C A suspend

    fun foo(): List<Response> = buildList { add(compute("A")) add(compute("B")) add(compute("C")) } fun main() = runBlocking { val list = foo() for (x in list) println(x) }
  19. fun CoroutineScope.foo(): ReceiveChannel<Response> = produce { send(compute("A")) send(compute("B")) send(compute("C")) }

    fun main() = runBlocking { val channel = foo() for (x in channel) println(x) }
  20. fun CoroutineScope.foo(): ReceiveChannel<Response> = produce { send(compute("A")) send(compute("B")) send(compute("C")) }

    fun main() = runBlocking { val channel = foo() for (x in channel) println(x) }
  21. fun CoroutineScope.foo(): ReceiveChannel<Response> = produce { send(compute("A")) send(compute("B")) send(compute("C")) }

    fun main() = runBlocking { val channel = foo() for (x in channel) println(x) }
  22. main() foo() fun CoroutineScope.foo(): ReceiveChannel<Response> = produce { send(compute("A")) send(compute("B"))

    send(compute("C")) } fun main() = runBlocking { val channel = foo() for (x in channel) println(x) }
  23. main() foo() fun CoroutineScope.foo(): ReceiveChannel<Response> = produce { send(compute("A")) send(compute("B"))

    send(compute("C")) } fun main() = runBlocking { val channel = foo() for (x in channel) println(x) }
  24. main() foo() Channel<R> fun CoroutineScope.foo(): ReceiveChannel<Response> = produce { send(compute("A"))

    send(compute("B")) send(compute("C")) } fun main() = runBlocking { val channel = foo() for (x in channel) println(x) }
  25. main() foo() Channel<R> fun CoroutineScope.foo(): ReceiveChannel<Response> = produce { send(compute("A"))

    send(compute("B")) send(compute("C")) } fun main() = runBlocking { val channel = foo() for (x in channel) println(x) }
  26. main() foo() Channel<R> fun CoroutineScope.foo(): ReceiveChannel<Response> = produce { send(compute("A"))

    send(compute("B")) send(compute("C")) } fun main() = runBlocking { val channel = foo() for (x in channel) println(x) }
  27. main() foo() send Channel<R> A fun CoroutineScope.foo(): ReceiveChannel<Response> = produce

    { send(compute("A")) send(compute("B")) send(compute("C")) } fun main() = runBlocking { val channel = foo() for (x in channel) println(x) }
  28. main() foo() send Channel<R> A A fun CoroutineScope.foo(): ReceiveChannel<Response> =

    produce { send(compute("A")) send(compute("B")) send(compute("C")) } fun main() = runBlocking { val channel = foo() for (x in channel) println(x) }
  29. main() foo() send send Channel<R> A A B fun CoroutineScope.foo():

    ReceiveChannel<Response> = produce { send(compute("A")) send(compute("B")) send(compute("C")) } fun main() = runBlocking { val channel = foo() for (x in channel) println(x) }
  30. main() foo() send send Channel<R> A B A B fun

    CoroutineScope.foo(): ReceiveChannel<Response> = produce { send(compute("A")) send(compute("B")) send(compute("C")) } fun main() = runBlocking { val channel = foo() for (x in channel) println(x) }
  31. main() foo() send send send Channel<R> A B A B

    C fun CoroutineScope.foo(): ReceiveChannel<Response> = produce { send(compute("A")) send(compute("B")) send(compute("C")) } fun main() = runBlocking { val channel = foo() for (x in channel) println(x) }
  32. main() foo() send send send Channel<R> A B C A

    B C fun CoroutineScope.foo(): ReceiveChannel<Response> = produce { send(compute("A")) send(compute("B")) send(compute("C")) } fun main() = runBlocking { val channel = foo() for (x in channel) println(x) }
  33. main() foo() send send send Channel<R> A B C A

    B C fun CoroutineScope.foo(): ReceiveChannel<Response> = produce { send(compute("A")) send(compute("B")) send(compute("C")) } fun main() = runBlocking { val channel = foo() for (x in channel) println(x) }
  34. main() foo() send send send Channel<R> A B C A

    B C fun CoroutineScope.foo(): ReceiveChannel<Response> = produce { send(compute("A")) send(compute("B")) send(compute("C")) } fun main() = runBlocking { val channel = foo() for (x in channel) println(x) }
  35. main() foo() send send send Channel<R> A B C A

    B C ❌ fun CoroutineScope.foo(): ReceiveChannel<Response> = produce { send(compute("A")) send(compute("B")) send(compute("C")) } fun main() = runBlocking { val channel = foo() for (x in channel) println(x) }
  36. fun main() = runBlocking { val channel = foo() for

    (x in channel) println(x) } Channel is hot
  37. Channel is hot fun main() = runBlocking { val channel

    = foo() // for (x in channel) println(x) }
  38. main() foo() Channel fun CoroutineScope.foo(): ReceiveChannel<Response> = produce { send(compute("A"))

    send(compute("B")) send(compute("C")) } fun main() = runBlocking { val channel = foo() // for (x in channel) println(x) }
  39. main() foo() send Channel A fun CoroutineScope.foo(): ReceiveChannel<Response> = produce

    { send(compute("A")) send(compute("B")) send(compute("C")) } fun main() = runBlocking { val channel = foo() // for (x in channel) println(x) }
  40. fun main() = runBlocking { val flow = foo() flow.collect

    { x -> println(x) } } fun foo(): Flow<Response> = flow { emit(compute("A")) emit(compute("B")) emit(compute("C")) }
  41. fun main() = runBlocking { val flow = foo() flow.collect

    { x -> println(x) } } fun foo(): Flow<Response> = flow { emit(compute("A")) emit(compute("B")) emit(compute("C")) }
  42. fun main() = runBlocking { val flow = foo() flow.collect

    { x -> println(x) } } fun foo(): Flow<Response> = flow { emit(compute("A")) emit(compute("B")) emit(compute("C")) }
  43. main() foo() fun main() = runBlocking { val flow =

    foo() flow.collect { x -> println(x) } } fun foo(): Flow<Response> = flow { emit(compute("A")) emit(compute("B")) emit(compute("C")) }
  44. main() foo() fun main() = runBlocking { val flow =

    foo() flow.collect { x -> println(x) } } fun foo(): Flow<Response> = flow { emit(compute("A")) emit(compute("B")) emit(compute("C")) }
  45. main() foo() Flow<R> fun main() = runBlocking { val flow

    = foo() flow.collect { x -> println(x) } } fun foo(): Flow<Response> = flow { emit(compute("A")) emit(compute("B")) emit(compute("C")) }
  46. main() foo() Flow<R> fun main() = runBlocking { val flow

    = foo() flow.collect { x -> println(x) } } fun foo(): Flow<Response> = flow { emit(compute("A")) emit(compute("B")) emit(compute("C")) }
  47. main() foo() collect Flow<R> fun main() = runBlocking { val

    flow = foo() flow.collect { x -> println(x) } } fun foo(): Flow<Response> = flow { emit(compute("A")) emit(compute("B")) emit(compute("C")) }
  48. main() foo() emit collect A Flow<R> fun main() = runBlocking

    { val flow = foo() flow.collect { x -> println(x) } } fun foo(): Flow<Response> = flow { emit(compute("A")) emit(compute("B")) emit(compute("C")) }
  49. main() foo() emit A collect A Flow<R> fun main() =

    runBlocking { val flow = foo() flow.collect { x -> println(x) } } fun foo(): Flow<Response> = flow { emit(compute("A")) emit(compute("B")) emit(compute("C")) }
  50. main() foo() emit A collect A Flow<R> fun main() =

    runBlocking { val flow = foo() flow.collect { x -> println(x) } } fun foo(): Flow<Response> = flow { emit(compute("A")) emit(compute("B")) emit(compute("C")) }
  51. main() foo() emit emit A collect A B Flow<R> fun

    main() = runBlocking { val flow = foo() flow.collect { x -> println(x) } } fun foo(): Flow<Response> = flow { emit(compute("A")) emit(compute("B")) emit(compute("C")) }
  52. main() foo() emit emit A B collect A B Flow<R>

    fun main() = runBlocking { val flow = foo() flow.collect { x -> println(x) } } fun foo(): Flow<Response> = flow { emit(compute("A")) emit(compute("B")) emit(compute("C")) }
  53. main() foo() emit emit A B collect A B Flow<R>

    fun main() = runBlocking { val flow = foo() flow.collect { x -> println(x) } } fun foo(): Flow<Response> = flow { emit(compute("A")) emit(compute("B")) emit(compute("C")) }
  54. main() foo() emit emit emit A B collect A B

    C Flow<R> fun main() = runBlocking { val flow = foo() flow.collect { x -> println(x) } } fun foo(): Flow<Response> = flow { emit(compute("A")) emit(compute("B")) emit(compute("C")) }
  55. main() foo() emit emit emit A B C collect A

    B C Flow<R> fun main() = runBlocking { val flow = foo() flow.collect { x -> println(x) } } fun foo(): Flow<Response> = flow { emit(compute("A")) emit(compute("B")) emit(compute("C")) }
  56. main() foo() emit emit emit A B C collect A

    B C Flow<R> fun main() = runBlocking { val flow = foo() flow.collect { x -> println(x) } } fun foo(): Flow<Response> = flow { emit(compute("A")) emit(compute("B")) emit(compute("C")) }
  57. main() foo() emit emit emit A B C collect A

    B C ❌ Flow<R> fun main() = runBlocking { val flow = foo() flow.collect { x -> println(x) } } fun foo(): Flow<Response> = flow { emit(compute("A")) emit(compute("B")) emit(compute("C")) }
  58. main() foo() emit emit emit A B C collect A

    B C ❌ Flow<R> fun main() = runBlocking { val flow = foo() flow.collect { x -> println(x) } } fun foo(): Flow<Response> = flow { emit(compute("A")) emit(compute("B")) emit(compute("C")) }
  59. fun main() = runBlocking { val flow = foo() flow.collect

    { x -> println(x) } } Flow is cold ❄
  60. fun main() = runBlocking { val flow = foo() //

    flow.collect { x -> println(x) } } Flow is cold ❄
  61. Flow is declarative fun foo(): Flow<Response> = flow { emit(compute("A"))

    emit(compute("B")) emit(compute("C")) } Declaration
  62. fun strings(): Flow<String> = flow { … } fun foo():

    Flow<Response> = strings().map { name -> compute(name) }
  63. fun strings(): Flow<String> = flow { … } fun foo():

    Flow<Response> = strings().map { name -> compute(name) }
  64. fun strings(): Flow<String> = flow { … } fun foo():

    Flow<Response> = strings().map { name -> compute(name) }
  65. fun strings(): Flow<String> = flow { … } fun foo():

    Flow<Response> = strings().map { name -> compute(name) } Operators
  66. Flow vs List fun foo(): Flow<Response> = flowOf("A", "B", "C").map

    { name -> compute(name) } suspend fun foo(): List<Response> = listOf("A", "B", "C").map { name -> compute(name) }
  67. fun foo(): Flow<Response> = flowOf("A", "B", "C").map { name ->

    compute(name) } suspend fun foo(): List<Response> = listOf("A", "B", "C").map { name -> compute(name) } Defined – Declarative Runs – Impera:ve suspend fun <T> Flow<T>.collect(…) Runs the flow Flow vs List
  68. fun foo(): Flow<Response> = flowOf("A", "B", "C").map { name ->

    compute(name) } suspend fun foo(): List<Response> = listOf("A", "B", "C").map { name -> compute(name) } Defined – Declara:ve Runs – Imperative suspend fun <T> Flow<T>.toList(): List<T> Runs the flow Flow vs List
  69. fun foo(): Flow<Response> = flowOf("A", "B", "C").map { name ->

    compute(name) } suspend fun foo(): List<Response> = listOf("A", "B", "C").map { name -> compute(name) } Flow vs List Execu6on order
  70. fun foo(): Flow<Response> = flowOf("A", "B", "C").map { name ->

    compute(name) } suspend fun foo(): List<Response> = listOf("A", "B", "C").map { name -> compute(name) } B C A Flow vs List
  71. fun foo(): Flow<Response> = flowOf("A", "B", "C").map { name ->

    compute(name) } suspend fun foo(): List<Response> = listOf("A", "B", "C").map { name -> compute(name) } B C A map Flow vs List
  72. fun foo(): Flow<Response> = flowOf("A", "B", "C").map { name ->

    compute(name) } suspend fun foo(): List<Response> = listOf("A", "B", "C").map { name -> compute(name) } B C A B’ C’ A’ map Flow vs List
  73. fun foo(): Flow<Response> = flowOf("A", "B", "C").map { name ->

    compute(name) } suspend fun foo(): List<Response> = listOf("A", "B", "C").map { name -> compute(name) } B C A B’ C’ A’ map A A’ Flow vs List
  74. fun foo(): Flow<Response> = flowOf("A", "B", "C").map { name ->

    compute(name) } suspend fun foo(): List<Response> = listOf("A", "B", "C").map { name -> compute(name) } B C A B’ C’ A’ map A B’ A’ B Flow vs List
  75. fun foo(): Flow<Response> = flowOf("A", "B", "C").map { name ->

    compute(name) } suspend fun foo(): List<Response> = listOf("A", "B", "C").map { name -> compute(name) } B C A B’ C’ A’ map A B’ C’ A’ B C Flow vs List React on emi2ed values
  76. fun <T : Any> Flow<T>.asPublisher(): Publisher<T> fun <T : Any>

    Publisher<T>.asFlow(): Flow<T> org.reactivestreams Publisher<T> kotlinx.coroutines.flow Flow<T>
  77. A A’ mapper fun map(mapper: (T) -> R): Flowable<R> fun

    flatMapSingle(mapper: (T) -> SingleSource<R>): Flowable<R> Synchronous Asynchronous Flowable<T>
  78. A A’ mapper fun map(mapper: (T) -> R): Flowable<R> fun

    flatMapSingle(mapper: (T) -> SingleSource<R>): Flowable<R> fun filter(predicate: (T) -> Boolean): Flowable<T> A A predicate Synchronous Asynchronous Synchronous Asynchronous Flowable<T>
  79. A A’ transform fun map(transform: suspend (T) -> R): Flow<R>

    Flow<T> fun filter(predicate: suspend (T) -> Boolean): Flow<T> A predicate A
  80. Operator avoidance startWith(value) onStart { emit(value) } delaySubscription(time) onStart {

    delay(time) } startWith(flow) onStart { emitAll(flow) } delayEach(time) onEach { delay(time) } onErrorReturn(value) catch { emit(value) } onErrorResume(flow) catch { emitAll(flow) } generate(…) flow { … } Composable
  81. flow.collect { value -> println(value) } val flow = flow

    { emit("A") } collect emit λ 1 2 λ
  82. flow.collect { value -> println(value) } val flow = flow

    { emit("A") } collect emit λ println 1 2 3 λ
  83. flow.collect { value -> println(value) } val flow = flow

    { emit("A") } collect λ emit λ println 1 2 3 ^ 4 ^ 5
  84. flow.collect { value -> println(value) } val flow = flow

    { emit("A") emit("B") } collect λ emit λ println emit println
  85. flow.collect { value -> println(value) } val flow = flow

    { emit("A") delay(100) emit("B") } collect λ emit λ println emit println Asynchronous emi@er
  86. flow.collect { value -> delay(100) println(value) } val flow =

    flow { emit("A") delay(100) emit("B") } collect λ emit λ println emit println Backpressure
  87. Kotlin Flow Plays Scrabble • Benchmark originally developed by José

    Paumard • Implemented for RxJava by David Karnok
  88. Kotlin Flow Plays Scrabble • Benchmark originally developed by José

    Paumard • Implemented for RxJava by David Karnok SequencePlaysScrabble 9.824 ± 0.190 ms/op https://github.com/Kotlin/kotlinx.coroutines/tree/develop/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/README.md
  89. Kotlin Flow Plays Scrabble • Benchmark originally developed by José

    Paumard • Implemented for RxJava by David Karnok SequencePlaysScrabble 9.824 ± 0.190 ms/op RxJava2PlaysScrabbleOpt 23.653 ± 0.379 ms/op https://github.com/Kotlin/kotlinx.coroutines/tree/develop/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/README.md
  90. Kotlin Flow Plays Scrabble • Benchmark originally developed by José

    Paumard • Implemented for RxJava by David Karnok SequencePlaysScrabble 9.824 ± 0.190 ms/op RxJava2PlaysScrabbleOpt 23.653 ± 0.379 ms/op FlowPlaysScrabbleOpt 13.958 ± 0.278 ms/op https://github.com/Kotlin/kotlinx.coroutines/tree/develop/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/README.md
  91. main() emit emit emit A B C collect A B

    C ❌ 100 ms 100 ms 100 ms 100 ms 100 ms 100 ms 100 ms 700 ms Single corouDne
  92. flow.buffer().collect { … } main() emit emit emit A B

    C collect A B C ❌ 100 ms 100 ms 100 ms 100 ms 400 ms 100 ms 100 ms 100 ms
  93. flow.buffer().collect { … } main() send send send A B

    C collect A B C ❌ Channel Declarative & safe
  94. fun foo(): Flow<Response> = flowOf("A", "B", "C").map { name ->

    compute(name) } fun main() = runBlocking { val flow = foo() flow.collect { x -> println(x) } } Where does it execute?
  95. fun foo(): Flow<Response> = flowOf("A", "B", "C").map { name ->

    compute(name) } .flowOn(Dispatchers.Default) fun main() = runBlocking { val flow = foo() flow.collect { x -> println(x) } } Executes in background
  96. fun foo(): Flow<Response> = flowOf("A", "B", "C").map { name ->

    compute(name) } .flowOn(Dispatchers.Default) Executes in collector’s context fun main() = runBlocking { val flow = foo() flow.collect { x -> println(x) } } Context preservation
  97. Observable • ………. • ….. • …….. subscribe SubscripIon val

    composite = CompositeDisposable() composite.add(observable.subscribe { event -> updateUI(event) })
  98. Observable • ………. • ….. • …….. subscribe SubscripIon val

    composite = CompositeDisposable() composite.add(observable.subscribe { event -> updateUI(event) }) composite.clear()
  99. Flow • ………. • ….. • …….. launch Job events()

    .onEach { event -> updateUI(event) } .launchIn(scope)
  100. Flow • ………. • ….. • …….. launch Job val

    scope = MainScope() events() .onEach { event -> updateUI(event) } .launchIn(scope)
  101. Flow • ………. • ….. • …….. launch Job val

    scope = MainScope() events() .onEach { event -> updateUI(event) } .launchIn(scope) scope.cancel()
  102. Flow • ………. • ….. • …….. launch Job val

    scope = MainScope() events() .onEach { event -> updateUI(event) } .launchIn(scope) scope.cancel()
  103. Status & Roadmap Flow is stable in kotlinx.corouInes version 1.3.0

    Future improvements ØOut-of-the box support for UI models (StateFlow / EventFlow) ØSharing / caching flows ØConcurrency / parallelism operators ØChunking / windowing operators Want more? Give us your feedback h2ps://github.com/Kotlin/kotlinx.corouInes/issues
  104. Learn more vKotlin Flow by example guide https://kotlinlang.org/docs/reference/coroutines/flow.html vAPI docs

    https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/ vStories in my blog https://medium.com/@elizarov