android-viewmodel について
Android Kotlin 開発者向けの ViewModel と状態管理のガイドです。StateFlow、SharedFlow、ライフサイクルに応じた収集方法を扱い、不変な UI 状態と明示的なバッキングフィールドを重視します。
主な機能
- 01StateFlow による永続 UI 状態
- 02SharedFlow による単発イベント
- 03ライフサイクル対応の Flow 収集
- 04viewModelScope での処理起動
- 05明示的なバッキングフィールドの優先
活用シーン
- 01画面の不変状態の設計
- 02ナビゲーションや Snackbar の処理
- 03Compose での状態とイベントの収集
- 04View でのライフサイクル別 Flow 収集
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ new-silvermoon/ awesome-android-agent-skills --skill android-viewmodelインストール後、Claude Code で/android-viewmodelを入力して読み込まれたか確認します。
詳細
Android ViewModel と状態管理
手順
ViewModel を使用して UI の状態とビジネスロジックを管理します。構成変更後も保持され、イミュータブルな API を公開する必要があります。
Kotlin 2.3+
従来の
_state/stateバッキングプロパティのパターンではなく、明示的なバッキングフィールドを使用してください。
- Kotlin 2.3:試験的機能(
-Xexplicit-backing-fields)- Kotlin 2.4+:安定版(推奨)
古い Kotlin バージョンを対象とする場合を除き、重複するバッキングプロパティの作成は避けてください。
1. UI の状態(StateFlow)
目的
永続的な UI の状態を表します。
例:
- 読み込み中
- 成功(データ)
- エラー(メッセージ)
- 空
宣言
明示的なバッキングフィールドを使用します。
val uiState: StateFlow<UiState>
field = MutableStateFlow(UiState.Loading)
バッキングフィールドはミュータブル(MutableStateFlow)で、利用側には StateFlow のみを公開します。
状態の更新
アトミックな更新には update { } を使用してください。
_uiState.update { old ->
old.copy(isLoading = false)
}
明示的なバッキングフィールドを使用する場合:
uiState.update {
it.copy(isLoading = false)
}
適切な場合は、直接代入することもできます。
uiState.value = UiState.Success(data)
2. 単発イベント(SharedFlow)
目的
再コンポーズや構成変更後に保持されない一時的なイベントに使用します。
例:
- 画面遷移
- スナックバーの表示
- トーストの表示
- ダイアログを開く
- 権限をリクエストする
宣言
val uiEvent: SharedFlow<UiEvent>
field = MutableSharedFlow(
replay = 0,
extraBufferCapacity = 1
)
replay = 0 にすると、再作成後にイベントが再生されません。
tryEmit() で送信する UI イベントには、extraBufferCapacity = 1 を推奨します。
イベントの送信
サスペンド関数:
uiEvent.emit(UiEvent.NavigateBack)
サスペンドしない場合:
uiEvent.tryEmit(UiEvent.ShowSnackbar("Saved"))
3. Compose での収集
StateFlow
必ずライフサイクルを考慮して収集します。
val state by viewModel.uiState.collectAsStateWithLifecycle()
SharedFlow
単一の LaunchedEffect 内で収集します。
LaunchedEffect(Unit) {
viewModel.uiEvent.collect { event ->
when (event) {
is UiEvent.NavigateBack -> { /* ... */ }
is UiEvent.ShowSnackbar -> { /* ... */ }
}
}
}
collectAsState() で SharedFlow を収集しないでください。
4. XML/View システムでの収集
ライフサイクルを考慮して収集します。
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect {
render(it)
}
}
}
イベントについても同様です。
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiEvent.collect(::handleEvent)
}
}
5. コルーチンスコープ
ViewModel の処理はすべて viewModelScope 内で起動します。
viewModelScope.launch {
repository.refresh()
}
ビジネスロジックは、UseCase または Repository に委譲することを推奨します。
6. 状態の設計
画面全体を、単一のイミュータブルな状態オブジェクトで表します。
data class UiState(
val isLoading: Boolean = false,
val items: List<Item> = emptyList(),
val error: String? = null
)
ライフサイクルが本当に異なる場合を除き、1 つの画面に対して相互に関連のない StateFlow を複数公開するのは避けてください。
7. ベストプラクティス
イミュータブルな API(StateFlow、SharedFlow)を公開する
明示的なバッキングフィールドを優先する(Kotlin 2.4+)
イミュータブルな UI 状態を使用する
状態の変更には update {} を使用する
単発イベントは SharedFlow で管理する
ビジネスロジックを Composable の中に置かない
collectAsStateWithLifecycle() を使用する
View では repeatOnLifecycle() を使用する
処理は viewModelScope 内で起動する
MutableStateFlow を公開しない
画面遷移イベントに StateFlow を使用しない
永続的な画面の状態に SharedFlow を使用しない
明示的なバッキングフィールドを使用できる場合、重複する _state プロパティを保持しない