Slide 1

Slide 1 text

Kotlin Coroutines Flow is coming

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

android_broadcast News for Android Devs

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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) }

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Channels

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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 ...

Slide 10

Slide 10 text

• 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

Slide 11

Slide 11 text

Flows

Slide 12

Slide 12 text

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/

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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) }

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

• 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/

Slide 19

Slide 19 text

• collect • single / Zrst • toList / toSet / toCollection • count • fold / reduce • launchIn / produceIn / broadcastIn Terminal Operators

Slide 20

Slide 20 text

Custom Operator

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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() }

Slide 24

Slide 24 text

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() }

Slide 25

Slide 25 text

Concurrent Flow

Slide 26

Slide 26 text

emi]er collector

Slide 27

Slide 27 text

emi]er collector

Slide 28

Slide 28 text

emi]er collector Coroutine A Coroutine B

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

Flow Constraints

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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
 } }

Slide 37

Slide 37 text

• 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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

Old ways adapter

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

Reactive Streams

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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)

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

Third-paNy library integration

Slide 51

Slide 51 text

@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

Slide 52

Slide 52 text

@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

Slide 53

Slide 53 text

@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>

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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>

Slide 57

Slide 57 text

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>

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

• 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?

Slide 61

Slide 61 text

Thank you! [email protected] @android_broadcast @kirill_rozov