$30 off During Our Annual Pro Sale. View Details »

Kotlin Coroutines. Flow is coming

Kotlin Coroutines. Flow is coming

As a part of kotlinx.coroutines 1.2 Flow was introduced as a cold asynchronous data stream. How does it work? What we can do with it? Is it that we waiting so long for replacing Rx? The talk has answers to them.

Kirill Rozov

August 03, 2019
Tweet

More Decks by Kirill Rozov

Other Decks in Programming

Transcript

  1. Kotlin Coroutines

    Flow is coming

    View Slide

  2. Kirill Rozov
    [email protected]
    Lead Android Developer@
    @kirill_rozov

    View Slide

  3. android_broadcast
    News for Android Devs

    View Slide

  4. 1. Channels

    2. Flows

    3. Custom operator

    4. Concurrent Flow

    5. Flow constraints

    6. Adapter

    7. Reactive Stream

    8. Backpressure

    9. Third paNy integrations
    Agenda

    View Slide

  5. Async in Kotlin
    // Single value
    suspend fun foo(p: Params): Value =
    withContext(Dispatchers.Default) { bar(p) }
    // Collection of values
    suspend fun foo(p: Params): List =
    buildList { while (hasMore) add(nextValue) }
    // Stream of values
    fun foo(p: Params): Sequence =
    sequence { while (hasMore) yield(nextValue) }

    View Slide

  6. Async in Kotlin
    // Single value
    suspend fun foo(p: Params): Value =
    withContext(Dispatchers.Default) { bar(p) }
    // Collection of values
    suspend fun foo(p: Params): List =
    buildList { while (hasMore) add(nextValue) }
    // Stream of values
    fun foo(p: Params): Sequence =
    sequence { while (hasMore) yield(nextValue) }
    Blocking

    View Slide

  7. Channels

    View Slide

  8. Channels
    fun CoroutineScope.fooProducer(p: Params)
    : ReceiveChannel {
    return produce { while (hasMore) send(nextValue) }
    }

    View Slide

  9. Channels
    fun CoroutineScope.fooProducer(p: Params)
    : ReceiveChannel {
    return produce { while (hasMore) send(nextValue) }
    }
    val values: ReceiveChannel = fooProducer(p)
    if (someCondition) {
    return anotherResult // Oops! Leaked channel
    }
    // ... do further work with values ...

    View Slide

  10. • Works with data sources that are intrinsically hot

    Data sources that exist without application’s requests for them

    • incoming network connections

    • event streams

    • Synchronization primitives

    • For transfer data between coroutines

    • Synchronize access to the same data from multiply coroutines
    Channels

    View Slide

  11. Flows

    View Slide

  12. Flow is a cold asynchronous data stream that
    executed sequentially (in the same coroutine)
    emits values and completes normally or with an
    exception
    kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/

    View Slide

  13. GlobalScope.launch {
    flowOf(1, 2, 3)
    .map { it * it }
    .collect { print(it) }
    }
    Flow

    View Slide

  14. Flow
    interface Flow {
    suspend fun collect(collector: FlowCollector)
    }

    View Slide

  15. Flow
    interface Flow {
    suspend fun collect(collector: FlowCollector)
    }
    interface FlowCollector {
    suspend fun emit(value: T)
    }

    View Slide

  16. Builders
    // From fixed set of values
    val flowA = flowOf(1, 2, 3)
    // From function type () -> T
    val flowB = { repeat(3) { it + 1 } }.asFlow()
    // Sequential call to the emit() function
    val flowC = flow {
    emit(1)
    emit(2)
    emit(3)
    }

    View Slide

  17. fun Iterator.asFlow(): Flow
    fun Iterable.asFlow(): Flow
    fun emptyFlow(): Flow
    fun Array.asFlow(): Flow
    fun IntRange.asFlow(): Flow
    fun Sequence.asFlow(): Flow
    fun (() -> T).asFlow(): Flow
    fun flow(
    block: suspend FlowCollector.() -> Unit
    ): Flow
    Builders

    View Slide

  18. • map / mapNotNull

    • switchMap

    • combineLatest

    • debounce / sample

    • delayEach / delayFlow

    • Zlter / ZlterNot / ZlterIsInstance / ZlterNotNull

    • zip

    • catch

    • onEach / onCompletion

    • \atMapConcat / \atMapMerge

    • \a]enConcat / \a]enMerge
    Intermediate

    Operators
    * Full list of available operators can be found in official documentation kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/

    View Slide

  19. • collect

    • single / Zrst

    • toList / toSet / toCollection

    • count

    • fold / reduce

    • launchIn / produceIn / broadcastIn
    Terminal
    Operators

    View Slide

  20. Custom Operator

    View Slide

  21. Built-in

    Operators
    interface Flow {
    suspend fun collect(collector: FlowCollector)
    }
    // Extension-oriented design approach
    fun Flow.map(
    transform: suspend (value: T) -> R
    ): Flow

    View Slide

  22. Custom

    Operator
    fun Flow.sqrts(): Flow {
    return flow { // this: FlowCollector
    collect { number ->
    val value = sqrt(number.toDouble())
    emit(value)
    }
    }
    }

    View Slide

  23. Custom

    Operator
    fun Flow.sqrts(): Flow {
    return flow { // this: FlowCollector
    collect { number ->
    val value = sqrt(number.toDouble())
    emit(value)
    }
    }
    }
    GlobalScope.launch {
    flowOf(1, 2, 3)
    .map { sqrt(it.toDouble()) }
    .sqrts()
    .collect()
    }

    View Slide

  24. Custom

    Operator
    fun Flow.sqrts(): Flow {
    return flow { // this: FlowCollector
    collect { number ->
    val value = sqrt(number.toDouble())
    emit(value)
    }
    }
    }
    GlobalScope.launch {
    flowOf(1, 2, 3)
    .map { sqrt(it.toDouble()) }
    .sqrts()
    .collect()
    }

    View Slide

  25. Concurrent Flow

    View Slide

  26. emi]er
    collector

    View Slide

  27. emi]er
    collector

    View Slide

  28. emi]er
    collector
    Coroutine A
    Coroutine B

    View Slide

  29. Concurrent Flow
    fun Flow.buffer(
    capacity: Int = Channel.UNLIMITED
    ): Flow = flow {
    val channel = coroutineScope.produce(capacity) {
    collect { send(it) }
    }
    channel.consumeEach { emit(it) }
    }

    View Slide

  30. Concurrent Flow
    fun Flow.buffer(capacity: Int): Flow
    fun Flow.produceIn(
    scope: CoroutineScope
    ): ReceiveChannel
    fun Flow.broadcastIn(
    scope: CoroutineScope,
    start: CoroutineStart = CoroutineStart.LAZY
    ): BroadcastChannel
    fun channelFlow(
    block: suspend ProducerScope.() -> Unit
    ): Flow

    View Slide

  31. Flow Constraints

    View Slide

  32. • Context preservation

    Flow encapsulates its own execution context and never propagates or leaks it
    downstream
    Flow Constraints

    View Slide

  33. Context

    Preservation
    flow {
    withContext(Dispatchers.Default) {
    emit(longComputation())
    }
    }

    View Slide

  34. Context

    Preservation
    flow {
    withContext(Dispatchers.Default) {
    emit(longComputation())
    }
    }
    java.lang.IllegalStateException: Flow invariant is violated: flow was collected in
    [Context A info], but emission happened in [Context B info]. Please refer to
    'flow' documentation or use 'flowOn' instead

    View Slide

  35. Context

    Preservation
    flow { emit(longComputation()) }
    .flowOn(Dispatchers.Default)

    View Slide

  36. Context

    Preservation
    flow {
    GlobalScope.launch(Dispatchers.IO) // is prohibited
    withContext(Dispatchers.Default) // is prohibited
    emit(1) // OK
    coroutineScope {

    emit(2) // OK -- still the same coroutine

    }
    }

    View Slide

  37. • Context preservation

    Flow encapsulates its own execution context and never propagates or leaks it
    downstream
    • Exception transparency

    Flow implementations never catch or handle exceptions that occur in
    downstream Flow Constraints

    View Slide

  38. flow {
    try {
    emitValue()
    } catch (e: Exception) {
    println("Error handled $e")
    }
    }
    .map { process1() }
    .collect { process2() }
    Exception

    Transparency

    View Slide

  39. flow { emitValue() }
    .map { process1() }
    // catches exceptions in emitValue() and process1()
    .catch { e -> println("Error handled $e") }
    // throws exceptions from process2()
    .collect { process2() }
    Exception

    Transparency

    View Slide

  40. Old ways adapter

    View Slide

  41. interface Stream {
    fun subscribe(callback: Callback)
    fun unsubscribe(callback: Callback)
    }
    interface Callback {
    fun onNext(item: D)
    fun onError(error: Throwable)
    fun onCompleted()
    }
    Old ways adapter

    View Slide

  42. val stream = Stream()
    callbackFlow { // this: ProducerScope
    // ProducerScope extends SendChannel
    val callback = object : Callback {
    override fun onNext(item: Value) { offer(item) }
    override fun onError(error: Throwable) { close(error) }
    override fun onCompleted() { close() }
    }
    stream.subscribe(callback)
    awaitClose { stream.unsubscribe(callback) }
    }
    Old ways adapter

    View Slide

  43. Reactive Streams

    View Slide

  44. RxJava Operator
    fun Observable.filter(
    predicate: (T) -> Boolean
    ): Observable = ObservableFilter(this, predicate)

    View Slide

  45. RxJava Operator
    class ObservableFilter(
    private val source: ObservableSource,
    private val predicate: (T) -> Boolean
    ) : Observable(), HasUpstreamObservableSource {
    override fun source(): ObservableSource = source
    override fun subscribeActual(observer: Observer) {
    source.subscribe(FilterObserver(observer, predicate))
    }
    internal class FilterObserver(
    actual: Observer,
    private val filter: (T) -> Boolean
    ) : BasicFuseableObserver(actual) {
    override fun onNext(t: T) {
    if (sourceMode == QueueFuseable.NONE) {
    try {
    if (filter(t)) downstream.onNext(t)
    } catch (e: Throwable) {
    fail(e)
    }
    } else {
    downstream.onNext(null)
    }
    }
    override fun requestFusion(mode: Int) = transitiveBoundaryFusion(mode)
    override fun poll(): T? {
    while (true) {
    val v = qd.poll()
    if (v == null || filter(v)) return v
    }
    }
    }
    }
    fun Observable.filter(
    predicate: (T) -> Boolean
    ): Observable = ObservableFilter(this, predicate)

    View Slide

  46. Flow Operator
    fun Flow.filter(
    predicate: suspend (T) -> Boolean
    ): Flow {
    return flow {
    collect {
    if (predicate(it)) emit(it)
    }
    }
    }

    View Slide

  47. Migration from
    RxJava
    RxJava 2 Coroutine
    Single Deferred !❄
    Maybe Deferred !❄
    Completable Job !❄
    Observable
    Channel !
    Flow ❄
    Flowable

    View Slide

  48. fun Observable.toFlow(): Flow
    fun Single.toFlow(): Flow
    fun Maybe.toFlow(): Flow
    fun Flowable.toFlow(): Flow
    RxJava2 to Flow

    Adapters

    View Slide

  49. fun Observable.toFlow(): Flow
    fun Single.toFlow(): Flow
    fun Maybe.toFlow(): Flow
    fun Flowable.toFlow(): Flow
    NO FROM THE BOX :) WRITE YOU OWN
    RxJava2 to Flow

    Adapters

    View Slide

  50. Third-paNy library
    integration

    View Slide

  51. @Dao
    interface SampleDao {
    }
    // Request data and observe changes with RxJava
    @Query("SELECT * FROM ENTITIES")
    fun queryEntities(): Observable>
    Room
    // Blocking request data
    @Query("SELECT * FROM ENTITIES")
    fun queryEntities(): List

    View Slide

  52. @Dao
    interface SampleDao {
    }
    // Request data and observe changes with RxJava
    @Query("SELECT * FROM ENTITIES")
    fun queryEntities(): Observable>
    Room
    // Blocking request data
    @Query("SELECT * FROM ENTITIES")
    fun queryEntities(): List
    // Request data using Coroutines
    @Query("SELECT * FROM ENTITIES")
    suspend fun queryEntities(): List

    View Slide

  53. @Dao
    interface SampleDao {
    }
    // Request data and observe changes with RxJava
    @Query("SELECT * FROM ENTITIES")
    fun queryEntities(): Observable>
    Room
    // Blocking request data
    @Query("SELECT * FROM ENTITIES")
    fun queryEntities(): List
    // Request data using Coroutines
    @Query("SELECT * FROM ENTITIES")
    suspend fun queryEntities(): List
    // Request data and observe changes using Coroutines
    @Query("SELECT * FROM ENTITIES")
    fun queryEntities(): Flow>

    View Slide

  54. val database = getSampleDatabase()
    GlobalScope.launch {
    database.sampleDao.queryEntities().collect {
    // Handle updates of entities
    }
    }
    Room

    View Slide

  55. interface GitHubService {
    }
    RetroZt
    @GET("users/{user}/repos")
    fun listRepos(
    @Path("user") user: String
    ): Call>

    View Slide

  56. interface GitHubService {
    }
    RetroZt @GET("users/{user}/repos")
    suspend fun listRepos(
    @Path("user") user: String
    ): List
    @GET("users/{user}/repos")
    fun listRepos(
    @Path("user") user: String
    ): Call>

    View Slide

  57. interface GitHubService {
    }
    RetroZt @GET("users/{user}/repos")
    suspend fun listRepos(
    @Path("user") user: String
    ): List
    @GET("users/{user}/repos")
    fun listRepos(
    @Path("user") user: String
    ): Flow>
    @GET("users/{user}/repos")
    fun listRepos(
    @Path("user") user: String
    ): Call>

    View Slide

  58. fun Call.asFlow(): Flow = callbackFlow {
    enqueue(object : Callback {
    override fun onFailure(call: Call, error: Throwable) {
    close(error)
    }
    override fun onResponse(call: Call, response: Response) {
    if (response.isSuccessful) {
    offer(response.body()!!)
    close()
    } else {
    close(RetrofitError(response.errorBody()))
    }
    }
    })
    awaitClose {
    if (!isCanceled) {
    cancel()
    }
    }
    }
    class RetrofitError(val errorBody: ResponseBody?) : Throwable()
    RetroZt

    View Slide

  59. val service = getGitHubService()
    GlobalScope.launch {
    service.listRepos("user-id")
    .asFlow()
    .collect {
    // handle response
    }
    }
    RetroZt

    View Slide

  60. • Flow will be primary way to handle async stream
    of data

    • Channels will be used:

    • for hot streams

    • “under-the-hood” in some Flow operators

    • as synchronization primitive between
    Coroutines
    What will be the
    next?

    View Slide

  61. Thank you!
    [email protected]
    @android_broadcast
    @kirill_rozov

    View Slide