FlowとStateFlowを用いた状態管理

## 1. はじめに
この記事では、Android開発におけるFlow・StateFlow・Stateを使った状態管理を、実際のTodoアプリを例にまとめています。間違いなどありましたらコメントでご指摘いただければ幸いです。

## 2. 各要素の概要

### Flow（データ監視）
```kotlin
@Query("SELECT * FROM tasks WHERE isDone = 0")
fun getUnfinishedTasks(): Flow<List<Task>>
```
Flowは非同期データストリームを扱うためのライブラリであり、複数のデータを非同期に出力することができます。すなわち、データの受け渡しをメインスレッドをブロックすることなく行うことが出来ます。また、Flowはコールドストリームとして設計されており、collect が呼び出されたときに初めてデータの取得が開始されます。

**用途**: データベースの変更監視やネットワークからのデータ取得

### StateFlow（画面状態管理）
```kotlin
private val _uiState = MutableStateFlow<AddTaskUiState>(AddTaskUiState.Input())
val uiState: StateFlow<AddTaskUiState> = _uiState
```
StateFlowはFlowの一種であり、**常に最新の値1つを保持する**という特徴を持っています。
常に最新の値を保持できる点やコンポーザブル関数側でStateに変換することで状態を監視し、変化に応じてリアクティブにデータの変更をUIに反映できる点などから、ViewModelでの状態管理に用いられています。
また、StateFlowは通常のFlowとは異なり、常に値を持ち続け、collectしたときにはすぐにその最新の値を受け取れるホットストリームという性質を持っています。

**用途**: ViewModelでの状態管理

### State（ローカルUI状態）
```kotlin
var showDatePicker by remember { mutableStateOf(false) }
```
Stateは、**JetpackCompose専用の状態管理の仕組み**です。Composeにおいて、UIの再描画のトリガーとしての役割を果たします。このStateである値が変更されると、その値を使用しているComposable関数が自動的にUIの再描画を行います。

**用途**: Compose内での一時的なUI状態の保持

## 3. 実装パターンと活用方法

### サンプルアプリの概要
まず、本記事で例として示すアプリの内容について簡単に説明しておきます。
今回使用するアプリは、
- タスク追加画面でのタスクの追加
- ホーム画面でのタスク一覧表示
- タスク編集画面での既存のタスクの編集
といった機能を備えた簡易的なTodoアプリになっています。

[Githubリポジトリはこちら](https://github.com/suzukibelltree/SampleToDoApp)

### 3.1 Repository層：Flowでデータ監視
```TaskDao.kt
@Query("SELECT * FROM tasks WHERE isDone = 0")
fun getUnfinishedTasks(): Flow<List<Task>>
```
上記のコードはタスクを保存するRoomデータベースを操作するためのDAOで定義されたメソッドの一つです。
Flowを返すDAOメソッドである`getUnfinishedTasks()`はRoomデータベースの変更(データの追加、削除、更新等)を検知し、クエリ結果に変化がある場合はFlow内で新しいList<Task>をemit(再送信)します。

### 3.2 ViewModel層：StateFlowで状態管理

#### パターン1: 単一画面の状態管理
```AddTaskViewModel.kt
sealed interface AddTaskUiState {
    // タスクの入力状態
    data class Input(
        val title: String = "",
        val deadline: String = "",
        val importance: TaskPriority = TaskPriority.MEDIUM,
    ) : AddTaskUiState

    // タスクの保存中状態
    data object Saving : AddTaskUiState

    // タスクの保存成功状態
    data object Success : AddTaskUiState
}

@HiltViewModel
class AddTaskViewModel @Inject constructor(
    private val taskRepository: TasksRepository
) :
    ViewModel() {
    // 内部保存用
    private val _uiState = MutableStateFlow<AddTaskUiState>(AddTaskUiState.Input())

    // 外部公開用(外部からは変更できない)
    val uiState: StateFlow<AddTaskUiState> = _uiState
    ....
}
```
`AddTaskUiState`はUIの状態を表現するためのsealed interfaceです。Inputはユーザーがタスクを入力している状態、Savingはタスク保存処理中の状態、Successは保存完了を示す状態です。InputにのみUI表示に必要な情報(タスクの名前、締め切り、重要度)を定義しています。
`AddTaskViewModel`ではこの`AddTaskUiState`の値を内部で更新する用の`_uiState`と外部に公開する用の`uiState`で2種類保持しています。`_uiState`は`MutableStateFlow`として保持され、privateであるためにViewModelの外部からは更新することが出来ません。`uiState`はStateFlowとして保持され、`_uiState`により更新されます。これにより、UI側からは状態の監視のみ可能にして不変性を保っています。

#### パターン2: 複数Flowの組み合わせ
```HomeViewModel.kt
sealed interface HomeUiState {
    // タスクの読み込み中状態
    data object Loading : HomeUiState

    // タスクの読み込み成功状態
    data class Success(
        val finishedTasks: List<Task>,
        val unfinishedTasks: List<Task>,
        val isEmpty: Boolean = false
    ) : HomeUiState
}

@HiltViewModel
class HomeViewModel @Inject constructor(
    private val tasksRepository: TasksRepository
) : ViewModel() {
    val uiState: StateFlow<HomeUiState> = combine(
        tasksRepository.getFinishedTasks(), // 2つのFlowのどちらかに変更があったら新しい値を生成
        tasksRepository.getUnfinishedTasks()
    ) { finishedTasks, unfinishedTasks ->
        // ホーム画面のUI状態を変更
        HomeUiState.Success(
            finishedTasks = finishedTasks,
            unfinishedTasks = unfinishedTasks,
            isEmpty = (finishedTasks.isEmpty() && unfinishedTasks.isEmpty())
        )
    }.stateIn( // FlowをStateFlowに変換する
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = HomeUiState.Loading
    )
    ....
}
```
上記のコードは、先ほどとは違って複数のFlowを組み合わせてUI状態を生成しています。
`HomeUiState`では、UIの状態として、タスクの読み込み中である`Loading`とタスクの読み込みが成功した状態であるSuccessを定義しており、`Success`では完了済みタスクと未完了のタスクがプロパティとして必要になります。
このように複数のFlowを組み合わせる場合は`combine`メソッドを利用します。この`combine`メソッドを用いることで複数のFlowを同時に監視し、そのうちいずれかのFlowでデータが更新されるたびにまとめて新しい値を生成します。
ここでは、完了したタスクのリストと未完了のタスクのリストのうちいずれかに変更があった場合に`HomeUiState.Success`を用いて最新のUI状態が生成されます。
`combine`メソッドで作成されたFlowはコールドストリームであるため、UI側で監視しやすくなるよう`stateIn`メソッドを用いてStateFlowに変換します。



### 3.3 Compose層：StateでUI制御
```AddTaskScreen.kt
@Composable
fun AddTaskScreen(viewModel: AddTaskViewModel = hiltViewModel()) {
    val uiState = viewModel.uiState.collectAsState()
    var showDatePicker by remember { mutableStateOf(false) }
    
    when (val state = uiState.value) {
        is AddTaskUiState.Input -> { /* 入力画面 */ }
        is AddTaskUiState.Saving -> CircularProgressIndicator()
    }
}
```
Composable関数側では、ViewModelがStateFlowとして持っているUI状態を`collectAsState()`関数を用いてStateに変換して保持します。
これにより、UIはStateの変化に自動的に反応して再コンポーズされ、常に最新の状態を表示することが可能になります。

## まとめ
この記事では、Flow,StateFlow,Stateを用いた状態管理についてまとめました。
このような状態管理の方法をとることにより、状態管理の複雑さを軽減したり、自動的なUIの更新を行ったりすることが出来ます。