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

KotlinCoroutinesFlowことはじめ

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for kr9ly kr9ly
November 27, 2019

 KotlinCoroutinesFlowことはじめ

Avatar for kr9ly

kr9ly

November 27, 2019
Tweet

More Decks by kr9ly

Other Decks in Programming

Transcript

  1. アジェンダ • Kotlin Coroutines Flowとは • Flowの使い方 • Cold Streamとは

    • Flowが導入されると何がうれしいのか? • RxJavaからのマイグレーション • まとめ
  2. アジェンダ • Kotlin Coroutines Flowとは • Flowの使い方 • Cold Streamとは

    • Flowが導入されると何がうれしいのか? • RxJavaからのマイグレーション • まとめ
  3. Kotlin Coroutines Flowとは Kotlin 1.2から導入された、 Kotlin Coroutinesで Cold Stream を取り扱うためのAPI

    まだExperimentalなAPIも多いが、実用的なレベルに達している(と思う)
  4. アジェンダ • Kotlin Coroutines Flowとは • Flowの使い方 • Cold Streamとは

    • Flowが導入されると何がうれしいのか? • RxJavaからのマイグレーション • まとめ
  5. Hello World! runBlocking { val f: Flow<String> = flow {

    emit("Hello") delay(500) emit("Coroutines Flow World!") } f.collect { value -> println(value) } } Hello Coroutines Flow World! 非同期処理をflowでラップ flow内で呼ぶ必要のある メソッドはemitのみ 使う側はcollectを呼ぶ
  6. suspend functionをflowに変換 runBlocking { val suspendFunc = suspend { delay(300)

    "this is suspend func result" } val f: Flow<String> = flow { emit(suspendFunc()) } f.collect { value -> println(value) } } public fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> = SafeFlow(block) flowに渡すfunctionはsuspend functionなので、そのままflow内で suspend functionを呼べばflowに変換できる
  7. どのDispatcher上で呼び出すかを変えられる runBlocking { val f = flow { emit(Thread.currentThread().name) }

    val f2 = flow { emit(Thread.currentThread().name) }.flowOn(Dispatchers.IO) f.collect { value -> println(value) } f2.collect { value -> println(value) } } (ちなみにflowOnはまだexperimentalみたいですが) main @coroutine#1 DefaultDispatcher-worker-2 @coroutine#2
  8. Exceptionのハンドリングもシンプル runBlocking { val f: Flow<String> = flow { emit("Hello")

    delay(500) throw RecoverableException() // throw UnRecoverableException() }.catch { t -> if (t is RecoverableException) { emit("Recovered") } else if (t is UnRecoverableException) { throw t } } f.collect { value -> println(value) } } Hello Recovered runBlocking { val f: Flow<String> = flow { emit("Hello") delay(500) // throw RecoverableException() throw UnRecoverableException() }.catch { t -> if (t is RecoverableException) { emit("Recovered") } else if (t is UnRecoverableException) { throw t } } f.collect { value -> println(value) } } Hello jp.dely.flowsample.UnRecoverableException
  9. FlowのAPIはほぼ拡張関数で定義されている 例えばcollectの定義 public suspend inline fun <T> Flow<T>.collect(crossinline action: suspend

    (value: T) -> Unit): Unit = collect(object : FlowCollector<T> { override suspend fun emit(value: T) = action(value) })
  10. アジェンダ • Kotlin Coroutines Flowとは • Flowの使い方 • Cold Streamとは

    • Flowが導入されると何がうれしいのか? • RxJavaからのマイグレーション • まとめ
  11. Hot Streamはもうある(interfaceは違うが) runBlocking { val channel = Channel<String>() launch {

    channel.send("first") delay(100) channel.send("second") delay(100) channel.send("third") delay(100) channel.send("fourth") delay(100) channel.send("fifth") channel.close() } for (value in channel) { println(value) } } > kotlinx.coroutines.channels.Channel <
  12. Flows are cold Flows are cold streams similar to sequences

    — the code inside a flow builder does not run until the flow is collected. https://kotlinlang.org/docs/reference/coroutines/flow.html#flows-are-cold RxJavaのユースケースもほとんどCold Stream
  13. 同じStreamを同時に呼び出す runBlocking { val f = flow { emit("first") delay(1000)

    emit("second") delay(1000) emit("third") } launch { f.collect { value -> println("a: $value") } } launch { delay(1000) f.collect { value -> println("b: $value") } } } a: first a: second b: first a: third b: second b: third a, b共に互いに影響しない (別のStreamが作られている)
  14. collect側で中断するとflow内でも中断される runBlocking { val f = flow { emit("first") println("first

    item emitted") delay(1000) emit("second") println("second item emitted") } try { withTimeout(500) { f.collect { value -> println(value) } } } catch (e: TimeoutCancellationException) { println("aborted") } } first first item emitted aborted Observerがいなくなれば処理をやめる
  15. アジェンダ • Kotlin Coroutines Flowとは • Flowの使い方 • Cold Streamとは

    • Flowが導入されると何がうれしいのか? • RxJavaからのマイグレーション • まとめ
  16. 非同期処理をコレクション操作のように扱う runBlocking { val f = flow { emit("a") delay(100)

    emit("b") delay(100) emit("c") } f.filterNot { value -> value == "b" }.map { value -> "transform: $value" }.collect { value -> println(value) } } transform: a transform: c コレクション操作用のAPIのメリットをほぼ享受できる • メソッド名から操作の目的が明確になる • 大体イメージ通りの動きをするはず
  17. Stream同士を合成できる(flatMap) 例えばAPIの呼び出し結果をもとに 別のAPIを呼ぶ際に使う • flatMapConcat (これがRxJava でいうflatMap) • flatMapMerge もあるがまだPreview

    (flatten系APIをどうするかをまだ ちゃんと決めきれて無さそう) runBlocking { val apiACall = flow { delay(1000) emit("WebAPIAの呼び出し結果") } val apiBCall: (String) -> Flow<String> = { result -> flow { delay(1000) emit("$result + WebAPIBの呼び出し結果") } } apiACall.flatMapLatest { result -> apiBCall(result) }.collect { result -> println(result) } } WebAPIAの呼び出し結果 + WebAPIBの呼び出し結果
  18. Stream同士を合成できる(zip) runBlocking { val uiEventCall = flow { delay(1000) emit("UIイベント1")

    emit("UIイベント2") } val apiCall = flow { delay(1000) emit("WebAPIの呼び出し結果1") emit("WebAPIの呼び出し結果2") } uiEventCall.zip(apiCall) { aResult, bResult -> "$aResult + $bResult" }.collect { result -> println(result) } } UIイベント1 + WebAPIの呼び出し結果1 UIイベント2 + WebAPIの呼び出し結果2 例えばUI操作の待ち合わせなど
  19. Stream同士を合成できる(combineLatest) runBlocking { val uiEventCall = flow { emit("UIイベント1") delay(500)

    emit("UIイベント2") delay(500) emit("UIイベント3") delay(500) emit("UIイベント4") } val apiCall = flow { emit("WebAPIの呼び出し結果1") delay(1000) emit("WebAPIの呼び出し結果2") } apiCall.combine(uiEventCall) { uiResult, apiResult -> "$uiResult, $apiResult" }.collect { result -> println(result) } } WebAPIの呼び出し結果1, UIイベント1 WebAPIの呼び出し結果1, UIイベント2 WebAPIの呼び出し結果2, UIイベント2 WebAPIの呼び出し結果2, UIイベント3 WebAPIの呼び出し結果2, UIイベント4 例えば常に更新されるタイプのデータ をUIイベントの度に更新しながら使う、 とか
  20. Streamを再利用しやすい class StreamsApi { fun returnAllValues(): Flow<String> = flow {

    emit("apple") emit("banana") emit("lemon") emit("orange") } fun returnOnlyApple(): Flow<String> = returnAllValues().filter { value -> value == "apple" } } 例えばちょっとfilterを追加した 別のAPIを作成したい、などあれば 既存のものを再利用して使ったり
  21. Streamに対する手続きを再利用しやすい • fun <T> retryConverter(): (Flow<T>) -> Flow<T> = {

    upstream -> upstream.retryWhen { cause, attempt -> println("cause: ${cause.javaClass.canonicalName}, attempt: $attempt") cause is NetworkException && attempt < 2 } } runBlocking { val f = flow<String> { throw NetworkException() } val f2 = flow<String> { throw InternalException() } try { f.let(retryConverter()).collect { value -> println(value) } } catch (e: NetworkException) { println("retried over 3 times.") } try { f2.let(retryConverter()).collect { value -> println(value) } } catch (e: InternalException) { println("internal exception occurred.") } } cause: jp.dely.flowsample.NetworkException, attempt: 0 cause: jp.dely.flowsample.NetworkException, attempt: 1 cause: jp.dely.flowsample.NetworkException, attempt: 2 retried over 3 times. cause: jp.dely.flowsample.InternalException, attempt: 0 internal exception occurred. Flow.letでFlowに対する任意の変換処理を適用 出来る(RxJavaのcomposeにあたる)
  22. アジェンダ • Kotlin Coroutines Flowとは • Flowの使い方 • Cold Streamとは

    • Flowが導入されると何がうれしいのか? • RxJavaからのマイグレーション • まとめ
  23. アジェンダ • Kotlin Coroutines Flowとは • Flowの使い方 • Cold Streamとは

    • Flowが導入されると何がうれしいのか? • RxJavaからのマイグレーション • まとめ
  24. まとめ • Kotlin Coroutines FlowはCoroutinesの仕組みの中でCold Streamを取り扱うためのAPIです • 1.3.2時点では色々なAPIがまだexperimentalですが、ライブラリを 作る、とかでなければ使ってしまっても大丈夫そうです •

    Flowを導入すると色々とうれしいことがあります • RxJavaからのマイグレーション用の仕組みがあります(RxJava知らな くても学習用に便利!)