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

«Coroutines Flow», Кирилл Розов, Epam

5d08ba0cd07942f2ddbf82e5b21ba5e7?s=47 FunCorp
August 03, 2019

«Coroutines Flow», Кирилл Розов, Epam

5d08ba0cd07942f2ddbf82e5b21ba5e7?s=128

FunCorp

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 // Builder // Intermediate operator // Terminal operator
  14. Flow interface Flow<out T> { suspend fun collect(collector: FlowCollector<T>) }

    interface FlowCollector<in T> { suspend fun emit(value: T) }
  15. 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) }
  16. 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
  17. • 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/
  18. • collect • single / Zrst • toList / toSet

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

  20. 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>
  21. 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() }
  22. Concurrent Flow

  23. emi]er collector

  24. emi]er collector

  25. emi]er collector Coroutine A Coroutine B

  26. 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) } }
  27. 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>
  28. Flow Constraints

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

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

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

  33. 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
 } }
  34. • 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
  35. flow<Any> { try { emitValue() } catch (e: Exception) {

    println("Error handled $e") } } .map { process1() } .collect { process2() } Exception Transparency
  36. 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
  37. Old ways adapter

  38. 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
  39. 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
  40. Reactive Streams

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

    Observable<T> = ObservableFilter(this, predicate)
  42. 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)
  43. Flow Operator fun <T> Flow<T>.filter( predicate: suspend (T) -> Boolean

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

    Deferred<T?> ❄ Completable Job ❄ Observable<T> Channel<T> Flow<T> ❄ Flowable<T>
  45. 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
  46. 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
  47. Third-paNy library integration

  48. @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>>
  49. val database = getSampleDatabase() GlobalScope.launch { database.sampleDao.queryEntities().collect { // Handle

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

    user: String ): Call<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>>
  51. 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
  52. val service = getGitHubService() GlobalScope.launch { service.listRepos("user-id") .asFlow() .collect {

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