0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Kotlin】StateFlow 徹底解説:UI状態をリアクティブに管理する

Posted at

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」 を実現するための標準設計

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?