$30 off During Our Annual Pro Sale. View Details »

5分でわかるKotlin Coroutines Flow

yagi
August 01, 2019

5分でわかるKotlin Coroutines Flow

- Kotlin CoroutinesにコールドストリームがなかったのでFlowが登場
- Channelはホットストリーム
- ホットストリームは結構簡単にリークする
- Flowは受信を開始しない限り動作しない
- Flowは実行する時に必ずコルーチンスコープに属するので閉じ忘れがない
- Flowにはオペレータがたくさんある
- ChannelはFlowの中で生き続ける
- FlowはだいたいRxJava
- Androidにおいては、RxJavaでストリームを扱っていたものをリプレースする時に使えそう

yagi

August 01, 2019
Tweet

More Decks by yagi

Other Decks in Technology

Transcript

  1. 5分でわかるKotlin Coroutines Flow
    sys1yagi
    Shibuya.apk 2019/08/01
    © 2019 Ubie, Inc. 1

    View Slide

  2. © 2019 Ubie, Inc.
    About Me
    2
    2
    ● 八木俊広
    ● @sys1yagi
    ● Software engineer at
    医療機関向け 業務効率化
    AI問診
    患者向け 病気推測
    Dr.Ubie

    View Slide

  3. © 2019 Ubie, Inc. 3
    5分でわかる Kotlin Coroutines Flow
    3
    ● Kotlin Coroutines Flowとは?
    ● ホットストリームとコールドストリーム
    ● ホットストリームとリソースのリーク
    ● 最初のFlow
    ● Flowの仕組み
    ● Flowのオペレータ
    ● Flowを使う(イベントストリーム)
    ● まとめ

    View Slide

  4. © 2019 Ubie, Inc. 4
    4
    Kotlin Coroutines Flowとは?

    View Slide

  5. © 2019 Ubie, Inc.
    Kotlin Coroutines Flowとは?
    5
    5
    Kotlin Coroutines 1.2.0-alpha-2からfeature previewで登場した、コールドストリーム。

    View Slide

  6. © 2019 Ubie, Inc.
    Kotlin Coroutinesが提供する機能たち
    6
    6
    ワンショット
    戻り値なし
    launch{}, Job
    ワンショット
    戻り値あり
    async{}, Deferred
    ホットストリーム
    (複数の値)
    Channel

    View Slide

  7. © 2019 Ubie, Inc.
    Kotlin Coroutinesが提供する機能たち
    7
    7
    ワンショット
    戻り値なし
    launch{}, Job
    ワンショット
    戻り値あり
    async{}, Deferred
    ホットストリーム
    (複数の値)
    Channel
    コールドストリーム
    (複数の値)
    Flow

    View Slide

  8. © 2019 Ubie, Inc. 8
    8
    ● 受信者の有無に関わらず動作を開始する
    ホットストリーム
    コールドストリーム
    ● 受信し始めない限り動作しない

    View Slide

  9. © 2019 Ubie, Inc.
    ホットストリームとChannel
    9
    9
    kotlinx.coroutines.channels.Channelはホットストリーム。

    View Slide

  10. © 2019 Ubie, Inc.
    1
    0
    10
    ホットストリームは
    取り扱いに気をつけないと
    結構簡単にリークしてしまう

    View Slide

  11. © 2019 Ubie, Inc.
    ホットストリームとリソースのリーク
    1
    1
    11
    fun TextView.textChangeAsChannel(): ReceiveChannel {
    val channel = Channel(Channel.CONFLATED)
    val textWatcher = addTextChangedListener {
    channel.offer(it?.toString())
    }
    channel.invokeOnClose {
    removeTextChangedListener(textWatcher)
    }
    return channel
    }
    TextViewの値の変化をChannelで受け取る関数

    View Slide

  12. © 2019 Ubie, Inc.
    ホットストリームとリソースのリーク
    1
    2
    12
    fun TextView.textChangeAsChannel(): ReceiveChannel {
    val channel = Channel(Channel.CONFLATED)
    val textWatcher = addTextChangedListener {
    channel.offer(it?.toString())
    }
    channel.invokeOnClose {
    removeTextChangedListener(textWatcher)
    }
    return channel
    }
    TextViewの値の変化をChannelで受け取る関数
    最新の値だけバッファする Channel

    View Slide

  13. © 2019 Ubie, Inc.
    ホットストリームとリソースのリーク
    1
    3
    13
    fun TextView.textChangeAsChannel(): ReceiveChannel {
    val channel = Channel(Channel.CONFLATED)
    val textWatcher = addTextChangedListener {
    channel.offer(it?.toString())
    }
    channel.invokeOnClose {
    removeTextChangedListener(textWatcher)
    }
    return channel
    }
    TextViewの値の変化をChannelで受け取る関数
    androidx.core:core-ktx:1.2.0-alpha02
    にある関数。

    View Slide

  14. © 2019 Ubie, Inc.
    ホットストリームとリソースのリーク
    1
    4
    14
    fun TextView.textChangeAsChannel(): ReceiveChannel {
    val channel = Channel(Channel.CONFLATED)
    val textWatcher = addTextChangedListener {
    channel.offer(it?.toString())
    }
    channel.invokeOnClose {
    removeTextChangedListener(textWatcher)
    }
    return channel
    }
    TextViewの値の変化をChannelで受け取る関数
    Channelがクローズする時に実行し、
    リスナーを解除する

    View Slide

  15. © 2019 Ubie, Inc.
    ホットストリームとリソースのリーク
    1
    5
    15
    lifecycleScope.launch {
    val onTextChange = edit.textChangeAsChannel()
    if (!readOnly) {
    onTextChange.consumeEach {
    button.isEnabled = !it.isNullOrEmpty()
    }
    }
    }
    TextViewの値の変化をChannelで受け取る関数

    View Slide

  16. © 2019 Ubie, Inc.
    ホットストリームとリソースのリーク
    1
    6
    16
    lifecycleScope.launch {
    val onTextChange = edit.textChangeAsChannel()
    if (!readOnly) {
    onTextChange.consumeEach {
    button.isEnabled = !it.isNullOrEmpty()
    }
    }
    }
    TextViewの値の変化をChannelで受け取る関数
    この時点で動き出す

    View Slide

  17. © 2019 Ubie, Inc.
    ホットストリームとリソースのリーク
    1
    7
    17
    lifecycleScope.launch {
    val onTextChange = edit.textChangeAsChannel()
    if (!readOnly) {
    onTextChange.consumeEach {
    button.isEnabled = !it.isNullOrEmpty()
    }
    }
    }
    TextViewの値の変化をChannelで受け取る関数
    trueでonTextChangeを使わない場合でもリスナーは動き続ける

    View Slide

  18. © 2019 Ubie, Inc.
    ホットストリームとリソースのリーク
    1
    8
    18
    lifecycleScope.launch {
    val onTextChange = edit.textChangeAsChannel()
    if (!readOnly) {
    onTextChange.consumeEach {
    button.isEnabled = !it.isNullOrEmpty()
    }
    }
    }
    TextViewの値の変化をChannelで受け取る関数
    trueでonTextChangeを使わない場合でもリスナーは動き続ける
    リーク!!

    View Slide

  19. © 2019 Ubie, Inc.
    1
    9
    19
    そこでコールドストリーム
    Kotlin Coroutines Flow

    View Slide

  20. © 2019 Ubie, Inc.
    Flowの導入
    2
    0
    20
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:
    1.3.0-RC"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:
    1.3.0-RC"
    APIは1.3.0-RCでStableになった。一部の関数に experimentalが残っている

    View Slide

  21. © 2019 Ubie, Inc.
    最初のFlow
    2
    1
    21
    fun intStream(): Flow = flow {
    repeat (10) {
    delay(10)
    emit(it)
    }
    }

    View Slide

  22. © 2019 Ubie, Inc.
    最初のFlow
    2
    2
    22
    fun intStream(): Flow = flow {
    repeat (10) {
    delay(10)
    emit(it)
    }
    }
    Flowを作成する関数

    View Slide

  23. © 2019 Ubie, Inc.
    最初のFlow
    2
    3
    23
    fun intStream(): Flow = flow {
    repeat (10) {
    delay(10)
    emit(it)
    }
    }
    ブロック内は
    suspend FlowCollector.() -> Unit

    View Slide

  24. © 2019 Ubie, Inc.
    最初のFlow
    2
    4
    24
    fun intStream(): Flow = flow {
    repeat (10) {
    delay(10)
    emit(it)
    }
    }
    任意のsuspend関数を呼び出せる

    View Slide

  25. © 2019 Ubie, Inc.
    最初のFlow
    2
    5
    25
    fun intStream(): Flow = flow {
    repeat (10) {
    delay(10)
    emit(it)
    }
    }
    値を送信するsuspend関数

    View Slide

  26. © 2019 Ubie, Inc.
    最初のFlow
    2
    6
    26
    GlobalScope.launch {
    val ints: Flow = intStream()
    ints.collect {
    println(it)
    }
    }

    View Slide

  27. © 2019 Ubie, Inc.
    最初のFlow
    2
    7
    27
    GlobalScope.launch {
    val ints: Flow = intStream()
    ints.collect {
    println(it)
    }
    }
    この時点で動作はしない

    View Slide

  28. © 2019 Ubie, Inc.
    最初のFlow
    2
    8
    28
    GlobalScope.launch {
    val ints: Flow = intStream()
    ints.collect {
    println(it)
    }
    }
    受信を開始するsuspend関数

    View Slide

  29. © 2019 Ubie, Inc.
    最初のFlow
    2
    9
    29
    GlobalScope.launch {
    val ints: Flow = intStream()
    ints.collect {
    println(it)
    }
    }
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9

    View Slide

  30. © 2019 Ubie, Inc.
    Flowの仕組み
    3
    0
    30
    GlobalScope.launch {
    val ints: Flow = intStream()
    ints.collect {
    println(it)
    }
    }
    public suspend inline fun Flow.collect(
    crossinline action: suspend (value: T) -> Unit
    ): Unit =
    collect(object : FlowCollector {
    override suspend fun emit(value: T) = action(value)
    })

    View Slide

  31. © 2019 Ubie, Inc.
    Flowの仕組み
    3
    1
    31
    fun intStream(): Flow = flow {
    repeat (10) {
    delay(10)
    emit(it)
    }
    }

    View Slide

  32. © 2019 Ubie, Inc.
    Flowの仕組み
    3
    2
    32
    fun intStream(): Flow = flow {
    repeat (10) {
    delay(10)
    emit(it)
    }
    }
    public fun flow(
    @BuilderInference block: suspend FlowCollector.() -> Unit
    ): Flow {
    return object : Flow {
    override suspend fun collect(collector: FlowCollector) {
    SafeCollector(collector, coroutineContext).block()
    }
    }
    }

    View Slide

  33. © 2019 Ubie, Inc.
    Flowの仕組み
    3
    3
    33
    suspend
    Flow.collect()
    suspend
    Flow#collect()
    SafeCollector(
    collector, coroutineContext
    ).block()
    flow (
    block: suspend FlowCollector.() -> Unit
    )
    fun intStream(): Flow = flow {
    repeat (10) {
    delay(10)
    emit(it)
    }
    }
    受信開始

    View Slide

  34. © 2019 Ubie, Inc.
    Flowの仕組み
    3
    4
    34
    ● Flow.collect関数をトリガーにして、値を発行するsuspend関数
    を実行する
    ● Flow.collect関数はsuspend関数なので必ずコルーチンスコー
    プに属する。そのため閉じ忘れがない
    ● 受信者毎に独立して動作する

    View Slide

  35. © 2019 Ubie, Inc.
    Flowのオペレータ
    3
    5
    35
    val ints: Flow = intStream()
    val processed = ints
    .filter { it % 2 == 0 }
    .map { it * 2 }
    .drop(1)
    .take(3)
    GlobalScope.launch {
    processed.collect {
    println("$it")
    }
    }

    View Slide

  36. © 2019 Ubie, Inc.
    Flowのオペレータ
    3
    6
    36
    val ints: Flow = intStream()
    val processed = ints
    .filter { it % 2 == 0 }
    .map { it * 2 }
    .drop(1)
    .take(3)
    GlobalScope.launch {
    processed.collect {
    println("$it")
    }
    }
    値を加工するオペレータ、suspend関数じゃ
    ないのでどこでも呼び出せる。
    関数ブロック内はsuspend関数

    View Slide

  37. © 2019 Ubie, Inc.
    Flowのオペレータ
    3
    7
    37
    val ints: Flow = intStream()
    val processed = ints
    .filter { it % 2 == 0 }
    .map { it * 2 }
    .drop(1)
    .take(3)
    GlobalScope.launch {
    processed.collect {
    println("$it")
    }
    }
    4
    8
    12

    View Slide

  38. © 2019 Ubie, Inc.
    Flowの末端オペレータ
    3
    8
    38
    GlobalScope.launch {
    val result = processed.first()
    }
    最初の1件を取り出す

    View Slide

  39. © 2019 Ubie, Inc.
    Flowの末端オペレータ
    3
    9
    39
    GlobalScope.launch {
    val result = processed.first()
    }
    collect(): Unit
    single(): T
    singleOrNULL(): T?
    toList(): List
    count(): Int
    fold(): T
    reduce(): T
    ...色々ある

    View Slide

  40. © 2019 Ubie, Inc.
    Flowを使ってイベントストリームを作る
    4
    0
    40
    public fun channelFlow(
    @BuilderInference
    block: suspend ProducerScope.() -> Unit
    ): Flow = ChannelFlowBuilder(block)
    値の送信にChannelを利用するFlowを作る関数

    View Slide

  41. © 2019 Ubie, Inc.
    Flowを使ってイベントストリームを作る
    4
    1
    41
    fun TextView.textChangeAsFlow(): Flow =
    channelFlow { this:ProducerScope
    channel.offer(text.toString())
    val textWatcher = addTextChangedListener {
    channel.offer(it?.toString())
    }
    awaitClose {
    removeTextChangedListener(textWatcher)
    }
    }.conflate()

    View Slide

  42. © 2019 Ubie, Inc.
    Flowを使ってイベントストリームを作る
    4
    2
    42
    fun TextView.textChangeAsFlow(): Flow =
    channelFlow { this:ProducerScope
    channel.offer(text.toString())
    val textWatcher = addTextChangedListener {
    channel.offer(it?.toString())
    }
    awaitClose {
    removeTextChangedListener(textWatcher)
    }
    }.conflate()
    プロパティにSendChannel
    をもっている

    View Slide

  43. © 2019 Ubie, Inc.
    Flowを使ってイベントストリームを作る
    4
    3
    43
    fun TextView.textChangeAsFlow(): Flow =
    channelFlow { this:ProducerScope
    channel.offer(text.toString())
    val textWatcher = addTextChangedListener {
    channel.offer(it?.toString())
    }
    awaitClose {
    removeTextChangedListener(textWatcher)
    }
    }.conflate()
    値を送信

    View Slide

  44. © 2019 Ubie, Inc.
    Flowを使ってイベントストリームを作る
    4
    4
    44
    fun TextView.textChangeAsFlow(): Flow =
    channelFlow { this:ProducerScope
    channel.offer(text.toString())
    val textWatcher = addTextChangedListener {
    channel.offer(it?.toString())
    }
    awaitClose {
    removeTextChangedListener(textWatcher)
    }
    }.conflate()
    ブロックを抜けるとChannelが終了する
    ので、受信者に閉じられるまで待つ

    View Slide

  45. © 2019 Ubie, Inc.
    Flowを使ってイベントストリームを作る
    4
    5
    45
    fun TextView.textChangeAsFlow(): Flow =
    channelFlow { this:ProducerScope
    channel.offer(text.toString())
    val textWatcher = addTextChangedListener {
    channel.offer(it?.toString())
    }
    awaitClose {
    removeTextChangedListener(textWatcher)
    }
    }.conflate() 常に最新の値を流す

    View Slide

  46. © 2019 Ubie, Inc.
    Flowを使ってイベントストリームを作る
    4
    6
    46
    lifecycleScope.launch {
    val onTextChange = edit.textChangeAsFlow()
    if (!readOnly) {
    onTextChange.collect {
    button.isEnabled = !it.isNullOrEmpty()
    }
    }
    }
    ここで初めて動き出す

    View Slide

  47. © 2019 Ubie, Inc.
    まとめ
    4
    7
    47
    ● Kotlin CoroutinesにコールドストリームがなかったのでFlowが登場
    ● Channelはホットストリーム
    ● ホットストリームは結構簡単にリークする
    ● Flowは受信を開始しない限り動作しない
    ● Flowは実行する時に必ずコルーチンスコープに属するので閉じ忘れがない
    ● Flowにはオペレータがたくさんある
    ● ChannelはFlowの中で生き続ける
    ● FlowはだいたいRxJava
    ● Androidにおいては、RxJavaでストリームを扱っていたものをリプレースする時に使えそう

    View Slide

  48. © 2019 Ubie, Inc.
    細かいところはテクブC96本で...!
    4
    8
    48
    https://techbooster.booth.pm/items/1485567

    View Slide