Kotlin独自の機能ピックアップ
Flowについて簡単にまとめてみました。
Flowとは?
コルーチンをベースにした非同期プログラミングのうち、
Flowは「連続的な非同期なデータ」を表現し、処理するための機能です。
そのため、Flowを非同期に処理を行える「データ ストリーム」と考えることもできます。
データ ストリームには、以下の 3 つのエンティティがあります。
プロデューサ
ストリームに出力するデータを生成します。コルーチンであるため、Flow は非同期にデータを生成できます。
インターミディアリ
ストリームに出力された各値またはストリームそのものを変更できます。(必要な場合)
コンシューマ
ストリームから得られた値を使用します。
引用(https://developer.android.com/kotlin/flow?hl=ja)
Flowの使い方
Flowを使うには、FlowビルダーAPIを使用します。
まずは以下のコードを見てみてください。
import kotlinx.coroutines
import kotlinx.coroutines.flow
fun sampleFlow() = flow {
for (i in 1..3) {
delay(1000) // 非同期処理の模擬
emit(i) // 値を生成
}
}
fun main() {
CoroutineScope(Dispatchers.IO).launch {
sampleFlow()
.collect { value ->
println(value)
}
}
}
// main()を呼んだ結果
1,2,3
これを細かく解説していきます。
sampleFlow()の処理
プロデューサの役割を果たし、ストリームに出力するデータを生成します。
Flow <Int>
Flow<Int>は、非同期で整数の連続的なストリームを表現するための型です
flow{~~~}
=でつながれているflow{~~~}は、Flowを生成するための flowビルダー関数です。
flowビルダー関数の中では、連続的な非同期なデータを処理します。
emit()
データを処理したあとは、emit()(直訳=放出)して値を生成します。
fun sampleFlow() = flow {
for (i in 1..3) {
delay(1000) // 非同期処理の模擬
emit(i) // 値を生成
}
}
今回のコードはiが3になるまでの処理を非同期でwhile(true){~}で繰り返しデータを生成し続けることも可能です。
しかし、あくまでもデータを生成しただけでこのままでは使用することができません。
main()の処理
CoroutineScope(Dispatchers.IO).launch{~~~}
非同期に処理を行うことができます。
sampleFlow().collect {~}
この中の処理は順序だてて説明します。
①一番最初にsampleFlow() 関数が呼ばれ、flow { ... } ビルダー内のコードが非同期に実行されます。しかしまだデータは発行されるわけではありません。
②simpleFlow()の戻り値である Flowインスタンスが得られます。
③.collect { println(it) } メソッドが呼ばれると、実際にデータの生成が開始されます。(トリガー)この時、flowビルダー内のコードが実行され、1から3までの整数が順次発行されます。
④各データ(Value)が発行されるたびに、collectメソッドのラムダ式が実行され、println(it)が呼ばれます
fun main() {
CoroutineScope(Dispatchers.IO).launch {
sampleFlow()
.collect { value ->
println(value)
}
}
}
その他のメソッド
Flow クラスには、さまざまなメソッドが提供されており、これらを組み合わせて
データを操作できます。
.map
.map メソッドは、各要素に対して変換関数を適用し、新しい要素のFlowを生成します。
しかし、実際に新しい要素を発行するのではなく、変換された要素が元の Flow の要素として発行されます。
// map 各要素を2倍に変換
sampleFlow()
.map { it * 2 }
.collect { println(it) }
// 出力:2,4,6
.filter:
.filter メソッドは、条件に合致する要素だけを含む新しい Flow を生成します。
// 2. filter: 条件を満たす要素だけを含む
simpleFlow()
.filter { it > 1 }
.collect { println(it) }
// 出力:2,3
.transform
.transform メソッドは、各要素に対して変換関数を適用し、新しい要素を発行するための柔軟なメソッドです。
// 3. transform: 各要素を2倍に変換
simpleFlow()
.transform { emit(it * 2) }
.collect { println(it) }
// 出力:2,4,6
.onEach
.onEach メソッドは、各要素に対して副作用を実行し、元の Flow をそのまま返します。
// 4. onEach: 各要素を処理しても元のFlowは変わらない
simpleFlow()
.onEach { println("Processing: $it") }
.collect { println(it) }
// 出力: Processing: 1,1, Processing: 2,2, Processing: 3,3
.zip
.zip メソッドは、複数の Flow を結合し、対応する位置の要素をペアとして新しい Flow を生成します。
// 5. zip: 2つのFlowを組み合わせて新しいFlowを生成
val flow1 = flowOf(1, 2, 3)
val flow2 = flowOf("A", "B", "C")
flow1.zip(flow2) { a, b -> "$a - $b" }
.collect { println("zip: $it") }
// 出力: zip: 1 - A, zip: 2 - B, zip: 3 - C
.flatMapConcat
.flatMapConcat メソッドは、各要素に対して非同期に別の Flow を生成し、それらの Flow を直列に結合します。
// 6. flatMapConcat: 各要素に対して別のFlowを生成し、直列に結合
simpleFlow()
.flatMapConcat { flowOf(it, it * 2, it * 3) }
.collect { println(it) }
// 出力: 1,2,3,2,4,6,3,6,9
最後に
flow難しい!