ViewModelを@Composableで使うときのcollectAsState()

Jetpack Composeにおいて、ViewModelのStateFlow(またはFlow)を@Composable内で購読し、UI更新をトリガーするComposeのStateに変換する際、Androidアプリ開発ではcollectAsState()ではなく、collectAsStateWithLifecycle()を使用することが推奨されています。 

それぞれの違いや推奨される実装方法を整理して解説します。

推奨される実装方法

現在、Android公式の 状態と Jetpack Compose ガイド では、アプリのリソース(CPUやメモリ、バッテリー)を節約するため、ライフサイクルを意識した収集を行う以下の構成が最も推奨されています。 

1. 依存関係の追加

collectAsStateWithLifecycle() を使用するには、build.gradle.kts に以下の依存関係が必要です。 

implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.x") // 適切な最新バージョンを指定

2. 実装コード例

collectAsState() と collectAsStateWithLifecycle() の違い

メソッド名ライフサイクル意識動作の特徴とリスク
collectAsStateWithLifecycle()あり(推奨)アプリがバックグラウンド(非表示)になると自動でFlowの収集を停止し、フォアグラウンドに戻ると再開する。無駄なリソース消費やバッテリドレインを防げる。
collectAsState()なしコンポジション(画面の描画ツリー)に存在する限り、アプリがバックグラウンドに隠れてもFlowの収集(データの受信や加工処理)を継続してしまう。

なぜ collectAsState() だけでは不十分なのか?

collectAsState() はComposeランタイムの機能であり、コンポジションのライフサイクル(画面に表示されているか)のみに従います。
しかし、Androidアプリには「ユーザーがホーム画面に戻った」「別のアプリが前面に開いた」といったAndroid OS固有のライフサイクルが存在します。画面が隠れていても、ViewModel内のFlowが動き続けてしまうと、位置情報の更新やデータ通信、重い計算処理などがバックグラウンドで走り続けてしまい、アプリのパフォーマンス低下を招きます。 

実装時のベストプラクティス

  • by キーワードで委譲するval uiState by ... と書くことで、.value を省略して直接オブジェクトにアクセスでき、コードが簡潔になります。
  • プレビュー用に状態を分離する@Preview を作成する際は、ViewModelを直接Composableに渡すのではなく、State とイベントコールバックだけを受け取る「ステートレスなComposable」に分離(状態ホイスティング)すると、テストやプレビューが容易になります。 
// 状態を保持する画面(ViewModelと依存)
@Composable
fun MyScreen(viewModel: MyViewModel = viewModel()) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    MyContent(uiState = uiState, onRefresh = { viewModel.refreshData() })
}

// UIの描画だけを担当(ViewModelに依存しないためPreview可能)
@Composable
fun MyContent(uiState: MyUiState, onRefresh: () -> Unit) {
    // 画面レイアウトの実装
}

FlowをCompose UIに接続する際は、特別な理由がない限りは Android Developers の推奨通り、lifecycle-runtime-compose ライブラリの collectAsStateWithLifecycle() を一貫して使用するようにしてください。 

タイトルとURLをコピーしました