Save 37% off PRO during our Black Friday Sale! »

KotlinCoroutinesFlowことはじめ

63e891c4f79dbdeb70aa8ebb8104c8d5?s=47 kr9ly
November 27, 2019

 KotlinCoroutinesFlowことはじめ

63e891c4f79dbdeb70aa8ebb8104c8d5?s=128

kr9ly

November 27, 2019
Tweet

Transcript

  1. Kotlin Coroutines Flowことはじめ 2019-11-27 dely株式会社 梅森翔(@kr9ly)

  2. delyはこういうの作ってる会社です

  3. 詳しく知りたければこのスライド見てください https://speakerdeck.com/tsubotax/dely

  4. 軽く自己紹介 • delyのAndroidアプリチームのリーダーやってます • 兼Engineering Manager • DroidKaigi2019に登壇したりしてます • 今年のは落ちました(残念)

    • Twitter: @kr9ly
  5. アジェンダ • Kotlin Coroutines Flowとは • Flowの使い方 • Cold Streamとは

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

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

    まだExperimentalなAPIも多いが、実用的なレベルに達している(と思う)
  8. 徐々にAPIはstableになっています https://github.com/Kotlin/kotlinx.coroutines/blob/master/CHANGES.md#version-132

  9. しかし1.3.2でもまだまだExperimental @ExperimentalCoroutinesApi ついてるAPIも結構あります (というか割とまだついてる) 使う場合は使うメソッド(かクラス)に @ExperimentalCoroutinesApi つける この発表は1.3.2時点でのFlow APIに準拠した説明をします

  10. ライブラリを作るとかでなければ心配無さそう https://github.com/Kotlin/kotlinx.coroutines/blob/1.3.2/docs/compat ibility.md#experimental-api

  11. ちなみに FlowはだいたいRxJavaです (親切にもMigration用の仕組みもあるので後でご紹介します)

  12. ところでCold Streamとは? 一旦置いといてまずは使い方から見てみましょう

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

    • Flowが導入されると何がうれしいのか? • RxJavaからのマイグレーション • まとめ
  14. Flowの使い方 特にライブラリを追加で入れる必要は無いです dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2'

  15. 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を呼ぶ
  16. 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に変換できる
  17. どの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
  18. 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
  19. 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) })
  20. アジェンダ • Kotlin Coroutines Flowとは • Flowの使い方 • Cold Streamとは

    • Flowが導入されると何がうれしいのか? • RxJavaからのマイグレーション • まとめ
  21. さて、Cold Streamとは? Hot Stream: 常にアクティブ、 Streamの下流にObserverがいな くても仕事する Cold Stream: Observerがいな

    いと仕事しない、Observerごとに Streamを作る
  22. 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 <
  23. Channelじゃいかんのか? 大抵のStreamは Cold Stream (=Observerが現れるまで仕事をする必要が無い) • 例えば非同期API呼び出し • 例えばUIイベント(イベントバス(=Hot Stream)で扱うこともできるが必ずしもする必要もない、

    例: RxBinding) • 例えばDBの更新通知 • あとChannel自体にはStreamとして扱う際の便利機能がほぼない…
  24. 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
  25. 同じ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が作られている)
  26. 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がいなくなれば処理をやめる
  27. アジェンダ • Kotlin Coroutines Flowとは • Flowの使い方 • Cold Streamとは

    • Flowが導入されると何がうれしいのか? • RxJavaからのマイグレーション • まとめ
  28. Flowが導入されると何がうれしいのか? • 非同期処理をコレクション操作のように扱うことが出来る • Stream同士を合成できる • Streamを再利用しやすい • Streamに対する手続きを再利用しやすい

  29. 非同期処理をコレクション操作のように扱う 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のメリットをほぼ享受できる • メソッド名から操作の目的が明確になる • 大体イメージ通りの動きをするはず
  30. 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の呼び出し結果
  31. 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操作の待ち合わせなど
  32. 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イベントの度に更新しながら使う、 とか
  33. 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を作成したい、などあれば 既存のものを再利用して使ったり
  34. 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にあたる)
  35. アジェンダ • Kotlin Coroutines Flowとは • Flowの使い方 • Cold Streamとは

    • Flowが導入されると何がうれしいのか? • RxJavaからのマイグレーション • まとめ
  36. RxJavaからのマイグレーション • 基本的なOperatorは(まだexperimentalだけど)あります • 無いものはIDE上で確認しながらマイグレーションできる仕組みがあ ります 先程のcompose -> letの例がこちら

  37. RxJavaからのマイグレーション • とりあえずRxJavaのOperatorを書くと、JavaDocに書き換え例がのっ てます • RxJava詳しくなくてもとりあえずRxJavaのOperatorを書いてみて JavaDoc読むと理解が早いかも(RxJavaのドキュメントは世の中に豊 富にあります!) ここに定義がすべてのってるので一旦これを読んでみるのもあり https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/flow/Migration.kt

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

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

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