1. StateFlowとは?
StateFlow は Kotlin Coroutines の一部として提供される、
「状態を持つリアクティブデータホルダー」 です。
つまり、
最新の状態を常に保持し、購読者(UIなど)にリアルタイムで配信するホットストリーム
として機能します。
簡単に言えば:
「常に最新の状態を1つだけ持つ
Flow」
「LiveDataの Coroutine 版」
2. 仕組みの概要
val state = MutableStateFlow(0)
// 状態更新
state.value = 10
// 購読
state.collect { value ->
println("最新値: $value")
}
特徴まとめ
| 項目 | 説明 |
|---|---|
| 型 | StateFlow<T> |
| 実装クラス | MutableStateFlow<T> |
| 値保持 | 常に最新1件を保持 |
| 初期値 |
必須(initialValue) |
| 送信メソッド |
value = ..., update { ... }
|
| 購読 |
collect { ... } でリアクティブ更新を受け取る |
| ホット | 常に動作(Flowと違い購読なしでも値を保持) |
3. StateFlowの基本動作イメージ
UI が再生成されても StateFlow は最新の状態を保持しているため、
画面の再描画や画面回転時でも即座に最新値を反映 できます。
4. 使用例:Compose × ViewModel
ViewModel 側
@HiltViewModel
class CounterViewModel @Inject constructor() : ViewModel() {
private val _count = MutableStateFlow(0)
val count: StateFlow<Int> = _count
fun increment() {
_count.update { it + 1 }
}
fun reset() {
_count.value = 0
}
}
UI 側(Compose)
@Composable
fun CounterScreen(vm: CounterViewModel = hiltViewModel()) {
val count by vm.count.collectAsState()
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("Count: $count")
Button(onClick = { vm.increment() }) {
Text("Increment")
}
Button(onClick = { vm.reset() }) {
Text("Reset")
}
}
}
ポイント
-
collectAsState()はStateFlowを Compose の再コンポーズトリガに変換 -
countが変わるたびに UI が自動で更新される
5. Flow → StateFlow 変換:stateIn
リポジトリ層などから Flow を受け取り、
UIで状態として扱いたい 場合は stateIn() を使います。
val userFlow: Flow<User> = repo.getUser()
val userState: StateFlow<User?> = userFlow
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = null
)
引数解説
| 引数 | 説明 |
|---|---|
scope |
どのスコープで共有するか(通常 viewModelScope) |
started |
共有開始条件 (WhileSubscribed 推奨) |
initialValue |
初期値(UI表示を安定化) |
WhileSubscribed により、購読者がいない間は上流の処理を停止できます。
これにより 無駄なデータ更新・リソース消費を防止。
6. StateFlow の設計指針(Clean Architecture視点)
| 層 | StateFlow の役割 |
|---|---|
| Domain層 | UseCaseはFlowを返す(純粋なデータストリーム) |
| ViewModel層 | Flow → StateFlowに昇格 (stateIn) |
| UI層 |
collectAsState()で購読・描画 |
7. よくある落とし穴
| 失敗例 | 問題点 | 正しい使い方 |
|---|---|---|
StateFlowで単発イベントを流す |
再購読で再発火(Sticky) |
SharedFlow(replay=0)を使う |
MutableStateFlowをFlow変換せず直接 collect
|
冗長・テスト困難 |
stateInでUI層に提供 |
initialValueを設定しない |
初期レンダリングにnullや空白 | 必ず初期値を与える |
8. StateFlowのテスト例
@OptIn(ExperimentalCoroutinesApi::class)
class CounterViewModelTest {
@get:Rule
val main = MainDispatcherRule()
@Test
fun increment_updates_count() = runTest {
val vm = CounterViewModel()
val results = mutableListOf<Int>()
val job = launch { vm.count.take(2).toList(results) }
vm.increment()
job.join()
assertEquals(listOf(0, 1), results)
}
}
まとめ
| 用途 | 推奨クラス | 特徴 |
|---|---|---|
| UI状態管理 | ✅ StateFlow
|
最新状態を保持・購読に強い |
| 単発イベント | ⚠️ SharedFlow / Channel
|
消費型イベントに強い |
| Flowを状態化 | stateIn() |
初期値付きの StateFlow に変換 |
- 「UI状態 = StateFlow」「イベント = SharedFlow」
-
StateFlowは ViewModel における “状態の単一ソース” として最適 - Compose との相性も抜群で、「状態を持つリアクティブなUI」 を実現するための標準設計