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

KotlinCoroutinesFlowことはじめ

kr9ly
November 27, 2019

 KotlinCoroutinesFlowことはじめ

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知らな くても学習用に便利!)