Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

© 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で受け取る関数

Slide 12

Slide 12 text

© 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

Slide 13

Slide 13 text

© 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 にある関数。

Slide 14

Slide 14 text

© 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がクローズする時に実行し、 リスナーを解除する

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

© 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が残っている

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

© 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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

© 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) } } 受信開始

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

© 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関数

Slide 37

Slide 37 text

© 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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

© 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 ...色々ある

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

© 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 をもっている

Slide 43

Slide 43 text

© 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() 値を送信

Slide 44

Slide 44 text

© 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が終了する ので、受信者に閉じられるまで待つ

Slide 45

Slide 45 text

© 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() 常に最新の値を流す

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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