[Android]Jetpack Compose から collectAsState 活用する

![スクリーンショット 2025-03-05 0.23.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3639815/5f9954c0-9887-41b8-82aa-29d42183ab7a.png)


Android開発において、Jetpack ComposeはUIを構築するための最新のアプローチを提供します。特に、状態管理はComposeアプリケーションにおいて重要な要素であり、これを効果的に管理するために、`ViewModel`と`collectAsState()`を組み合わせて使用する方法について学びましょう。

## 📌 collectAsState()

`collectAsState()`は、Jetpack Composeにおいて`Flow`（または `StateFlow`を簡単に購読し、その結果をComposeの`State`に変換する拡張関数です。この関数を使用すると、`Flow`が新しい値を`emit`するたびにUIが自動的に再コンポジションされ、最新の状態が表示されます。

#### なぜ使用するのか？

- **Kotlin Coroutines**の**Flow**は、非同期データをストリーミング方式で提供します。
- しかし、Composeでは`State`の変化を監視してUIを更新する必要があります。
- `collectAsState()`を使用すると、`Flow`を直接`launch`して`collect`する必要がなく、自動的に`Flow`を購読し、現在の値をComposeの状態として扱うことができます。

## 📌 ViewModel と Flow

#### ViewModelでFlowを使用して状態を公開するパターン

```kotlin
class MainViewModel : ViewModel() {

    private val _uiState = MutableStateFlow(MyUiState())
    val uiState: StateFlow<MyUiState> = _uiState.asStateFlow()

    fun updateSomething(newValue: String) {
        _uiState.update { currentState ->
            currentState.copy(something = newValue)
        }
    }
}
```

- `MutableStateFlow` : 内部で変更可能な状態を定義
- `StateFlow` : 外部には読み取り専用で公開


### collectAsState() の理解

```kotlin
@Composable
fun MainScreen(viewModel: MainViewModel = viewModel()) {

    val uiState by viewModel.uiState.collectAsState()

    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Column {
            Text(text = "Current value: ${uiState.something}")

            Button(onClick = { viewModel.updateSomething("New Value") }) {
                Text("Update Value")
            }

            if (uiState.isLoading) {
                CircularProgressIndicator()
            }

            uiState.error?.let { error ->
                Text(
                    text = error,
                    color = MaterialTheme.colorScheme.error
                )
            }
        }
    }
}
```

- `uiState` : FlowをStateに変換
- Compose が自動的にFlow購読のライフサイクルを管理
- コンポーザブルがCompositionから削除されたとき、Flowの購読も自動的にキャンセル

>これで uiState は State<MyUiState> 型となり、
viewModel.uiState の値が変更されるたびにこのコンポーザブルが再構成されます。
>


## 📌 collectAsState を使わない場合の比較

```kotlin
@Composable
fun MainScreen(viewModel: MainViewModel = viewModel()) {

    var uiState by remember { mutableStateOf(MyUiState()) }
  
    LaunchedEffect(viewModel) {
        viewModel.uiState.collect { newState ->
            uiState = newState
        }
    }

    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Column {
            Text(text = "Current value: ${uiState.something}")

            Button(onClick = { viewModel.updateSomething("New Value") }) {
                Text("Update Value")
            }

            if (uiState.isLoading) {
                CircularProgressIndicator()
            }

            uiState.error?.let { error ->
                Text(
                    text = error,
                    color = MaterialTheme.colorScheme.error
                )
            }
        }
    }
}
```

- LaunchedEffectを使用して手動でコルーチンのライフサイクルを管理する必要がある。
- 実装を誤るとメモリリークの原因になる可能性がある。


## 📌 複数の Flow を処理する場合

#### collectAsState() を使用:
```kotlin
val state1 by flow1.collectAsState()
val state2 by flow2.collectAsState()
val state3 by flow3.collectAsState()
```
#### collectAsState() を使わない場合:
```kotlin
var state1 by remember { mutableStateOf(initialValue1) }
var state2 by remember { mutableStateOf(initialValue2) }
var state3 by remember { mutableStateOf(initialValue3) }

LaunchedEffect(Unit) {
    launch { flow1.collect { state1 = it } }
    launch { flow2.collect { state2 = it } }
    launch { flow3.collect { state3 = it } }
}
```
- collectAsState() を使用すると、各 Flow を個別に管理でき、コードが明確になる。

## 📌 初期値の処理

#### collectAsState() を使用:
```kotlin
val state by flow.collectAsState(initial = initialValue)
```
#### collectAsState() を使わない場合:
```kotlin
var state by remember { mutableStateOf(initialValue) }
LaunchedEffect(Unit) {
    flow.collect { state = it }
}
```

- collectAsState() は Flow の最初の値が収集される前に表示する初期値を直接指定可能。

## 📌 エラー処理

#### collectAsState() を使用:

- エラー処理は内部で自動的に行われるため、特別な処理は不要。

#### collectAsState() を使わない場合:
```kotlin
LaunchedEffect(Unit) {
    try {
        flow.collect { state = it }
    } catch (e: Exception) {
        // error handling
    }
}
```
- Flowの収集中に発生した例外を手動で処理する必要がある。

## 📌 まとめ

### パフォーマンス比較

**メモリ使用量**: どちらの方法も類似したメモリ使用量を示しますが、`collectAsState()`は内部的に最適化されており、若干効率的な場合があります。

**再コンポーズの効率性**: `collectAsState()`はComposeの状態管理システムと密接に統合されており、変更があった部分のみ効率的に再コンポーズされます。

**開発者の効率性**: `collectAsState()`を使用すると、ボイラープレートコードを減らし、コアのビジネスロジックに集中することができます。

### 使用シナリオの比較

#### collectAsState()が適しているケース:
- シンプルなUI状態を監視する場合
- 複数の独立したFlowを監視する場合
- 標準的なパターンに従うアプリで使用する場合
- コードの簡潔さや可読性を重視する場合

#### collectAsState()を使用しない方が適しているケース:
- 複雑なFlowの変換や結合が必要な場合
- 特別なエラーハンドリングが必要な場合
- Flowの収集に対して細かい制御が必要な場合
- 特定のコルーチンスコープでFlowを収集する必要がある場合（ただし、`collectAsState(context = ...`)を使用することで対応可能）


#### 結論
`collectAsState()`は、Jetpack Composeで`Flow`を監視する最もシンプルかつ効率的な方法です。従来の方法と比較すると、コードがより簡潔になり、ライフサイクル管理が自動化され、複数の`Flow`を容易に処理できるというメリットがあります。

しかし、特別な要件がある場合には、従来の方法を使用して`Flow`の収集をより細かく制御することも可能です。それぞれの方法のメリット・デメリットを理解し、状況に応じて適切に選択することが重要です。

---

GitHub : https://github.com/GEUN-TAE-KIM/collectAsState_study