shareInとstateInでCold FlowをHotに変換する
共有演算子を使ってCold FlowをHotストリームに変換します。
「shareInとstateInでCold FlowをHotに変換する」はCoddyKit上の無料Kotlin Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはKotlin Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Kotlin Academyコースには全4レッスンが含まれています。
Cold FlowとHot Flowの違い
Cold Flowはコレクターごとに再起動しますが、Hot Flowは1つの上流サブスクリプションを共有します。Cold FlowをHot Flowに変換すると、複数のコレクターが購読したときの重複処理を避けられます。
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun expensiveFlow() = flow {
println("Starting upstream work") // runs once if shared
repeat(3) { delay(100); emit(it) }
}
fun main() = runBlocking {
val cold = expensiveFlow()
// Two collectors = two executions:
launch { cold.collect { } }
launch { cold.collect { } }
delay(500)
coroutineContext.cancelChildren()
}shareInの基本
flow.shareIn(scope, started, replay)はCold FlowをSharedFlowに変換し、1つの上流サブスクリプションを共有します。
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val cold = flow {
println("Upstream started once")
repeat(5) { delay(100); emit(it) }
}
val hot = cold.shareIn(this, SharingStarted.Eagerly, replay = 0)
launch { hot.collect { println("A: $it") } }
launch { hot.collect { println("B: $it") } }
delay(600)
coroutineContext.cancelChildren()
}stateInの基本
flow.stateIn(scope, started, initialValue)はStateFlowに変換します。StateFlowは常に値を持ち、1つの値をリプレイします。
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val cold = flow { delay(100); emit(42) }
val state: StateFlow<Int> = cold.stateIn(
scope = this,
started = SharingStarted.Eagerly,
initialValue = 0
)
println(state.value) // 0 immediately
delay(200)
println(state.value) // 42 after upstream emits
coroutineContext.cancelChildren()
}SharingStarted.Eagerly
Eagerlyでは、サブスクライバーの有無にかかわらず、shareIn/stateInが呼び出された時点ですぐに上流が開始されます。
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val flow = flow {
println("Eagerly started")
emit(1)
}.shareIn(this, SharingStarted.Eagerly)
// Upstream already running even before any collect
delay(50)
launch { flow.collect { println(it) } }
delay(100)
coroutineContext.cancelChildren()
}SharingStarted.Lazily
Lazilyでは、最初のサブスクライバーが購読した時点で上流が開始され、その後はサブスクライバーが0になっても停止しません。
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val flow = flow {
println("Lazily started on first subscriber")
repeat(3) { delay(100); emit(it) }
}.shareIn(this, SharingStarted.Lazily, replay = 1)
delay(50) // no subscriber yet — not started
launch { flow.collect { println(it) } } // triggers start
delay(400)
coroutineContext.cancelChildren()
}SharingStarted.WhileSubscribed
WhileSubscribed(stopTimeout)では、最初のサブスクライバーが参加すると上流が開始され、最後のサブスクライバーが離れてからstopTimeoutミリ秒後に停止します。ViewModelに最適です。
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val flow = flow {
println("Started")
repeat(10) { delay(100); emit(it) }
}.shareIn(this, SharingStarted.WhileSubscribed(500))
val job = launch { flow.collect { print("$it ") } }
delay(300)
job.cancel() // subscriber left
delay(200) // within 500ms stop timeout — still running
launch { flow.collect { print("resume $it ") } }
delay(500)
coroutineContext.cancelChildren()
}一般的なViewModelパターン
AndroidのViewModelでは、stateIn(viewModelScope, WhileSubscribed(5000), initialValue)を使ってリポジトリのFlowを変換すると、共有しながら構成変更後も処理を継続できます。
import kotlinx.coroutines.flow.*
// In ViewModel:
// val uiState: StateFlow<UiState> = repository
// .dataFlow()
// .map { UiState.Success(it) }
// .stateIn(
// scope = viewModelScope,
// started = SharingStarted.WhileSubscribed(5_000),
// initialValue = UiState.Loading
// )
fun main() { println("WhileSubscribed(5000) is the recommended ViewModel pattern") }shareInとstateInの比較
shareIn → SharedFlow(現在値なし、リプレイ数を設定可能)。stateIn → StateFlow(常に値を持ち、replay=1)。利用側が現在の状態を必要とするかどうかに基づいて選択します。
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val cold = (1..3).asFlow().map { it * 10 }
// SharedFlow — no initial value:
val shared: SharedFlow<Int> = cold.shareIn(this, SharingStarted.Eagerly, replay = 1)
// StateFlow — always has a value:
val state: StateFlow<Int> = cold.stateIn(this, SharingStarted.Eagerly, 0)
delay(100)
println("shared cache: ${shared.replayCache}")
println("state value: ${state.value}")
coroutineContext.cancelChildren()
}Replayキャッシュのトレードオフ
replayの値を大きくすると、後から購読したサブスクライバーがより多くの履歴を受け取れる一方で、より多くのメモリを使用します。UIの状態にはreplay=1(stateIn)で十分です。イベントログでは、より大きなreplayが必要になる場合があります。
import kotlinx.coroutines.flow.*
// replay=0: no history, only future events
// replay=1: last value (equivalent to stateIn)
// replay=N: last N events — use for message feeds, logs
// Trade-off: memory vs subscriber freshness
fun main() { println("Choose replay based on late-subscriber requirements") }共有しないFlowのリスク
共有しない場合、Composeの各コレクターやViewModelの各オブザーバーが上流処理を再実行するため、ネットワーク呼び出しやデータベースクエリが重複します。負荷の高いFlowは必ず共有してください。
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun expensiveApi() = flow {
println("API CALL") // without sharing: once per collector
emit("data")
}
fun main() = runBlocking {
val shared = expensiveApi().shareIn(this, SharingStarted.Lazily, replay = 1)
launch { shared.collect { } } // one API call, shared
launch { shared.collect { } } // same emission
delay(200)
coroutineContext.cancelChildren()
}共有時のリソース解放
共有Flowのスコープがキャンセルされると、上流のFlowもキャンセルされ、そのfinallyブロックが実行されます。これにより、リソースが適切に解放されます。
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun resourceFlow() = flow {
try { repeat(10) { delay(100); emit(it) } }
finally { println("Upstream cleaned up") }
}
fun main() = runBlocking {
val scope = CoroutineScope(SupervisorJob())
val shared = resourceFlow().shareIn(scope, SharingStarted.Eagerly)
launch { shared.collect { print("$it ") } }
delay(250)
scope.cancel() // upstream cleanup runs
delay(100)
}確認問題
AndroidのViewModelには、どのSharingStarted戦略が推奨されますか。
まとめ
shareInはSharedFlowに変換し、stateInはStateFlowに変換します。ViewModelではWhileSubscribedを使用してリソースを節約します。共有すると、複数のコレクターが購読したときに上流処理が重複して実行されるのを防げます。
よくある質問
「shareInとstateInでCold FlowをHotに変換する」レッスンは無料ですか?
はい。「shareInとstateInでCold FlowをHotに変換する」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Kotlin Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Kotlin Academyコースには全4レッスンが含まれています。
「shareInとstateInでCold FlowをHotに変換する」で何を学びますか?
共有演算子を使ってCold FlowをHotストリームに変換します。 ブラウザで直接実行するハンズオンコードでKotlin Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Kotlin Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのKotlin Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「shareInとstateInでCold FlowをHotに変換する」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このKotlin Academyレッスンでコードを書いて実行できますか?
はい。すべてのKotlin Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- StateFlow:UI向けのHotな状態保持
- SharedFlow:イベントバスとワンショットイベント
- shareInとstateInでCold FlowをHotに変換する
- TurbineによるStateFlowとSharedFlowのテスト