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

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.

2aec47eb9a940c619f05972f0db5aa00?s=128

Kirill Rozov

August 03, 2019
Tweet

Transcript

  1. Kotlin Coroutines Flow is coming

  2. Kirill Rozov krl.rozov@gmail.com Lead Android Developer@ @kirill_rozov

  3. android_broadcast News for Android Devs

  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
  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<Value> = buildList { while (hasMore) add(nextValue) } // Stream of values fun foo(p: Params): Sequence<Value> = sequence { while (hasMore) yield(nextValue) }
  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<Value> = buildList { while (hasMore) add(nextValue) } // Stream of values fun foo(p: Params): Sequence<Value> = sequence { while (hasMore) yield(nextValue) } Blocking
  7. Channels

  8. Channels fun CoroutineScope.fooProducer(p: Params) : ReceiveChannel<Value> { return produce {

    while (hasMore) send(nextValue) } }
  9. Channels fun CoroutineScope.fooProducer(p: Params) : ReceiveChannel<Value> { return produce {

    while (hasMore) send(nextValue) } } val values: ReceiveChannel<Value> = fooProducer(p) if (someCondition) { return anotherResult // Oops! Leaked channel } // ... do further work with values ...
  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
  11. Flows

  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/
  13. GlobalScope.launch { flowOf(1, 2, 3) .map { it * it

    } .collect { print(it) } } Flow
  14. Flow interface Flow<out T> { suspend fun collect(collector: FlowCollector<T>) }

  15. Flow interface Flow<out T> { suspend fun collect(collector: FlowCollector<T>) }

    interface FlowCollector<in T> { suspend fun emit(value: T) }
  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) }
  17. fun <T> Iterator<T>.asFlow(): Flow<T> fun <T> Iterable<T>.asFlow(): Flow<T> fun <T>

    emptyFlow(): Flow<T> fun <T> Array<T>.asFlow(): Flow<T> fun IntRange.asFlow(): Flow<Int> fun <T> Sequence<T>.asFlow(): Flow<T> fun <T> (() -> T).asFlow(): Flow<T> fun <T> flow( block: suspend FlowCollector<T>.() -> Unit ): Flow<T> Builders
  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/
  19. • collect • single / Zrst • toList / toSet

    / toCollection • count • fold / reduce • launchIn / produceIn / broadcastIn Terminal Operators
  20. Custom Operator

  21. Built-in
 Operators interface Flow<out T> { suspend fun collect(collector: FlowCollector<T>)

    } // Extension-oriented design approach fun <T, R> Flow<T>.map( transform: suspend (value: T) -> R ): Flow<R>
  22. Custom
 Operator fun <T : Number> Flow<T>.sqrts(): Flow<Double> { return

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

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

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

  26. emi]er collector

  27. emi]er collector

  28. emi]er collector Coroutine A Coroutine B

  29. Concurrent Flow fun <T> Flow<T>.buffer( capacity: Int = Channel.UNLIMITED ):

    Flow<T> = flow { val channel = coroutineScope.produce(capacity) { collect { send(it) } } channel.consumeEach { emit(it) } }
  30. Concurrent Flow fun <T> Flow<T>.buffer(capacity: Int): Flow<T> fun <T> Flow<T>.produceIn(

    scope: CoroutineScope ): ReceiveChannel<T> fun <T> Flow<T>.broadcastIn( scope: CoroutineScope, start: CoroutineStart = CoroutineStart.LAZY ): BroadcastChannel<T> fun <T> channelFlow( block: suspend ProducerScope<T>.() -> Unit ): Flow<T>
  31. Flow Constraints

  32. • Context preservation
 Flow encapsulates its own execution context and

    never propagates or leaks it downstream Flow Constraints
  33. Context Preservation flow { withContext(Dispatchers.Default) { emit(longComputation()) } }

  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
  35. Context Preservation flow { emit(longComputation()) } .flowOn(Dispatchers.Default)

  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
 } }
  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 <ows Flow Constraints
  38. flow<Any> { try { emitValue() } catch (e: Exception) {

    println("Error handled $e") } } .map { process1() } .collect { process2() } Exception Transparency
  39. flow<Any> { emitValue() } .map { process1() } // catches

    exceptions in emitValue() and process1() .catch { e -> println("Error handled $e") } // throws exceptions from process2() .collect { process2() } Exception Transparency
  40. Old ways adapter

  41. interface Stream<D> { fun subscribe(callback: Callback<D>) fun unsubscribe(callback: Callback<D>) }

    interface Callback<D> { fun onNext(item: D) fun onError(error: Throwable) fun onCompleted() } Old ways adapter
  42. val stream = Stream<Value>() callbackFlow<Value> { // this: ProducerScope<Value> //

    ProducerScope<Value> extends SendChannel<Value> val callback = object : Callback<Value> { 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
  43. Reactive Streams

  44. RxJava Operator fun <T> Observable<T>.filter( predicate: (T) -> Boolean ):

    Observable<T> = ObservableFilter(this, predicate)
  45. RxJava Operator class ObservableFilter<T>( private val source: ObservableSource<T>, private val

    predicate: (T) -> Boolean ) : Observable<T>(), HasUpstreamObservableSource<T> { override fun source(): ObservableSource<T> = source override fun subscribeActual(observer: Observer<in T>) { source.subscribe(FilterObserver(observer, predicate)) } internal class FilterObserver<T>( actual: Observer<in T>, private val filter: (T) -> Boolean ) : BasicFuseableObserver<T, T>(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 <T> Observable<T>.filter( predicate: (T) -> Boolean ): Observable<T> = ObservableFilter(this, predicate)
  46. Flow Operator fun <T> Flow<T>.filter( predicate: suspend (T) -> Boolean

    ): Flow<T> { return flow { collect { if (predicate(it)) emit(it) } } }
  47. Migration from RxJava RxJava 2 Coroutine Single<T> Deferred<T> !❄ Maybe<T>

    Deferred<T?> !❄ Completable Job !❄ Observable<T> Channel<T> ! Flow<T> ❄ Flowable<T>
  48. fun <T> Observable<T>.toFlow(): Flow<T> fun <T> Single<T>.toFlow(): Flow<T> fun <T>

    Maybe<T>.toFlow(): Flow<T> fun <T> Flowable<T>.toFlow(): Flow<T> RxJava2 to Flow Adapters
  49. fun <T> Observable<T>.toFlow(): Flow<T> fun <T> Single<T>.toFlow(): Flow<T> fun <T>

    Maybe<T>.toFlow(): Flow<T> fun <T> Flowable<T>.toFlow(): Flow<T> NO FROM THE BOX :) WRITE YOU OWN RxJava2 to Flow Adapters
  50. Third-paNy library integration

  51. @Dao interface SampleDao { } // Request data and observe

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

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

    changes with RxJava @Query("SELECT * FROM ENTITIES") fun queryEntities(): Observable<List<Entity>> Room // Blocking request data @Query("SELECT * FROM ENTITIES") fun queryEntities(): List<Entity> // Request data using Coroutines @Query("SELECT * FROM ENTITIES") suspend fun queryEntities(): List<Entity> // Request data and observe changes using Coroutines @Query("SELECT * FROM ENTITIES") fun queryEntities(): Flow<List<Entity>>
  54. val database = getSampleDatabase() GlobalScope.launch { database.sampleDao.queryEntities().collect { // Handle

    updates of entities } } Room
  55. interface GitHubService { } RetroZt @GET("users/{user}/repos") fun listRepos( @Path("user") user:

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

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

    user: String ): List<Repo> @GET("users/{user}/repos") fun listRepos( @Path("user") user: String ): Flow<List<Repo>> @GET("users/{user}/repos") fun listRepos( @Path("user") user: String ): Call<List<Repo>>
  58. fun <T> Call<T>.asFlow(): Flow<T> = callbackFlow { enqueue(object : Callback<T>

    { override fun onFailure(call: Call<T>, error: Throwable) { close(error) } override fun onResponse(call: Call<T>, response: Response<T>) { if (response.isSuccessful) { offer(response.body()!!) close() } else { close(RetrofitError(response.errorBody())) } } }) awaitClose { if (!isCanceled) { cancel() } } } class RetrofitError(val errorBody: ResponseBody?) : Throwable() RetroZt
  59. val service = getGitHubService() GlobalScope.launch { service.listRepos("user-id") .asFlow() .collect {

    // handle response } } RetroZt
  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?
  61. Thank you! krl.rozov@gmail.com @android_broadcast @kirill_rozov