Copenhagen Denmark
ASYNCHRONOUS DATA STREAMS WITH KOTLIN FLOW ROMAN ELIZAROV
@relizarov
Copenhagen Denmark
ASYNCHRONOUS DATA STREAMS WITH KOTLIN FLOW ROMAN ELIZAROV - - PowerPoint PPT Presentation
ASYNCHRONOUS DATA STREAMS WITH KOTLIN FLOW ROMAN ELIZAROV @relizarov Copenhagen Copenhagen Denmark Denmark RECAP ON KOTLIN COROUTINES Kotlin Coroutines FTW Callback hell before fun requestTokenAsync(): Promise<Token> { } fun
Copenhagen Denmark
@relizarov
Copenhagen Denmark
Kotlin Coroutines FTW
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) } }
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
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) }
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) }
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) }
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) }
suspend fun foo(): Response One response suspend fun foo(): List<Response> Many responses
suspend fun foo(): List<Response>
suspend fun foo(): List<Response> = buildList { … }
suspend fun foo(): List<Response> = buildList { add(compute("A")) add(compute("B")) add(compute("C")) }
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) }
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) }
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) }
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) }
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) }
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) }
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
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
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
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
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) }
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) }
receive() send()
fun CoroutineScope.foo(): ReceiveChannel<Response> = produce { … }
fun CoroutineScope.foo(): ReceiveChannel<Response> = produce { send(compute("A")) send(compute("B")) send(compute("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) }
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) }
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) }
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) }
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) }
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) }
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) }
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) }
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) }
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) }
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) }
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) }
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) }
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) }
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) }
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) }
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) }
fun main() = runBlocking { val channel = foo() for (x in channel) println(x) }
fun main() = runBlocking { val channel = foo() // for (x in channel) println(x) }
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) }
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) }
fun CoroutineScope.foo(): ReceiveChannel<Response>
🔦
Image: Markus Trienke, Sunset over dri6 ice
fun foo(): Flow<Response> = flow { … }
fun foo(): Flow<Response> = flow { … }
fun foo(): Flow<Response> = flow { emit(compute("A")) emit(compute("B")) emit(compute("C")) }
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")) }
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")) }
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")) }
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")) }
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")) }
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")) }
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")) }
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")) }
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")) }
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")) }
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")) }
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")) }
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")) }
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")) }
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")) }
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")) }
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")) }
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")) }
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")) }
fun main() = runBlocking { val flow = foo() flow.collect { x -> println(x) } }
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")) }
Declaration
fun strings(): Flow<String> = flow { emit("A") emit("B") emit(”C") }
fun strings(): Flow<String> = flow { … } fun foo(): Flow<Response> = strings().map { name -> compute(name) }
fun strings(): Flow<String> = flow { … } fun foo(): Flow<Response> = strings().map { name -> compute(name) }
fun strings(): Flow<String> = flow { … } fun foo(): Flow<Response> = strings().map { name -> compute(name) }
fun strings(): Flow<String> = flow { … } fun foo(): Flow<Response> = strings().map { name -> compute(name) }
Operators
fun foo(): Flow<Response> = flowOf("A", "B", "C").map { name -> compute(name) }
Operators
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) }
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
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
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) }
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
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
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
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’
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
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
React on emi2ed values
RxJava Project Reactor Kotlin Flow Reactive Streams Specification
Publisher<T>
Publisher<T>
fun <T : Any> Publisher<T>.asFlow(): Flow<T>
Publisher<T> kotlinx.coroutines.flow Flow<T>
fun <T : Any> Flow<T>.asPublisher(): Publisher<T> fun <T : Any> Publisher<T>.asFlow(): Flow<T>
Publisher<T> kotlinx.coroutines.flow Flow<T>
What’s the difference?
A A’
mapper fun map(mapper: (T) -> R): Flowable<R> fun flatMapSingle(mapper: (T) -> SingleSource<R>): Flowable<R>
Synchronous Asynchronous
Flowable<T>
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>
A A’
Flow<T> fun map(transform: suspend (T) -> R): Flow<R> transform
A A’
transform Flow<T> fun map(transform: suspend (T) -> R): Flow<R>
A A’
transform fun map(transform: suspend (T) -> R): Flow<R> Flow<T> fun filter(predicate: suspend (T) -> Boolean): Flow<T>
A
predicate
A
startWith(value)
delaySubscription(time)
startWith(flow)
delayEach(time)
catch { emit(value) }
catch { emitAll(flow) }
generate(…)
flow { … }
Composable
Image: Flow by Grant Tarrant
interface Flow<out T> { suspend fun collect(collector: FlowCollector<T>) } interface FlowCollector<in T> { suspend fun emit(value: T) }
flow.collect { value -> println(value) } collect
1
flow.collect { value -> println(value) } val flow = flow { emit("A") } collect
1
flow.collect { value -> println(value) } val flow = flow { emit("A") } collect
1
flow.collect { value -> println(value) } val flow = flow { emit("A") } collect
1
λ
flow.collect { value -> println(value) } val flow = flow { emit("A") } collect
1
λ
flow.collect { value -> println(value) } val flow = flow { emit("A") } collect emit
λ
1 2
λ
flow.collect { value -> println(value) } val flow = flow { emit("A") } collect emit
λ
println
1 2 3
λ
flow.collect { value -> println(value) } val flow = flow { emit("A") } collect
λ
emit
λ
println
1 2 3
^
4
^
5
flow.collect { value -> println(value) } val flow = flow { emit("A") emit("B") } collect
λ
emit
λ
println emit println
flow.collect { value -> println(value) } val flow = flow { emit("A") delay(100) emit("B") } collect
λ
emit
λ
println emit println
Asynchronous emi@er
flow.collect { value -> delay(100) println(value) } val flow = flow { emit("A") delay(100) emit("B") } collect
λ
emit
λ
println emit println
Backpressure
SequencePlaysScrabble 9.824 ± 0.190 ms/op
https://github.com/Kotlin/kotlinx.coroutines/tree/develop/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/README.md
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
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
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
Image: Channels by Tom Doel
flow.buffer().collect { … }
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
flow.buffer().collect { … } main() emit emit emit
A B C
collect
A B C
Separate corouDne
flow.buffer().collect { … } main() send send send
A B C
collect
A B C ❌
Channel
Declarative & safe
Image: Context by Bart Everson
fun foo(): Flow<Response> = flowOf("A", "B", "C").map { name -> compute(name) }
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?
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
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
fun events(): Flow<Event>
fun events(): Flow<Event> scope.launch { … }
fun events(): Flow<Event> scope.launch { events().collect { event -> … } }
fun events(): Flow<Event> scope.launch { events().collect { event -> updateUI(event) } }
fun events(): Flow<Event> scope.launch { events().collect { event -> updateUI(event) } }
“Subscribe” to events
fun events(): Flow<Event> events() .onEach { event -> updateUI(event) } .launchIn(scope)
“Subscribe” to events
fun events(): Flow<Event> events() .onEach { event -> updateUI(event) } .launchIn(scope)
“Subscribe” to events
Observable
Observable
subscribe
updateUI(event) }
Observable
subscribe
SubscripIon
updateUI(event) }
Observable
subscribe
SubscripIon
val composite = CompositeDisposable() composite.add(observable.subscribe { event -> updateUI(event) })
Observable
subscribe
SubscripIon
val composite = CompositeDisposable() composite.add(observable.subscribe { event -> updateUI(event) }) composite.clear()
Flow
launch
Job CorouIneScope Structured Concurrency
Flow
launch
Job
events() .onEach { event -> updateUI(event) } .launchIn(scope)
Flow
launch
Job
val scope = MainScope() events() .onEach { event -> updateUI(event) } .launchIn(scope)
Flow
launch
Job
val scope = MainScope() events() .onEach { event -> updateUI(event) } .launchIn(scope) scope.cancel()
Flow
launch
Job
val scope = MainScope() events() .onEach { event -> updateUI(event) } .launchIn(scope) scope.cancel()
LiveData
data.observe(livecycleOwner) { event -> updateUI(event) }
What’s next?
🚁 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
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
#KotlinConf
Roman Elizarov @relizarov #KotlinConf