ViewModel は、Activity と Fragment のライフサイクルを考慮して UI データを保存・管理するために設計された Android Jetpack Architecture コンポーネントです。Google I/O 2025 によると、Jetpack 上に構築された最新の Android アプリの 82% で ViewModel が使用されています。通常のクラスとは異なり、ViewModel は画面回転やその他の構成変更を自動的に生き延び、データを失うことなく UI 状態を維持します。MVVM (Model-View-ViewModel) アーキテクチャは、ビジネスロジックとインターフェースを結ぶ中心的な層として ViewModel に依存しています。
重要なポイント
ViewModel は、ユーザーインターフェースに関連するデータを保存・管理するために設計された Android Jetpack ライブラリのクラスで、Activity や Fragment のライフサイクルを考慮します。ViewModel の主な役割は、データ準備ロジックを UI 層から分離し、画面回転、テーマ変更、ロケール変更などの構成変更時にこのデータを保持することです。
ViewModel が登場する前は、開発者は UI 状態を Activity や Fragment に直接保存していました。画面を回転すると、Android は Activity を破棄して新しいものを作成し、保存されていないデータはすべて失われていました。解決策としては、onSaveInstanceState() による状態保存や onRetainNonConfigurationInstance() の使用がありましたが、どちらのアプローチも手動管理とシリアル化が必要で、複雑なオブジェクトには適していませんでした。ViewModel はフレームワークレベルでこの問題を解決します。データは UI とは別にメモリに保持され、Activity の再作成時に自動的に復元されます。
Android Developers のドキュメント (2025) によると、ViewModel はプロセスの RAM にデータを保存します。これは、バイト配列へのシリアル化が必要な onSaveInstanceState() による Bundle からの復元よりも 10~50 倍高速 です。ViewModel は、データが単純なプリミティブや文字列よりも複雑なすべての画面で推奨されます。
ViewModel のライフサイクル は Activity のライフサイクルとは根本的に異なります。ViewModel は画面回転時に破棄されず、スコープが完全に終了するまで (Activity.finish() または Fragment の削除) 生存します。つまり、ViewModel に読み込まれたデータは、ネットワークやデータベースから再読み込みすることなく、構成変更中も利用可能です。
Activity 作成時に、システムは ViewModelProvider を介して ViewModel を割り当てます。ViewModelProvider.get(ViewModel::class.java) の最初の呼び出しで新しい ViewModel インスタンスが作成されます。後続の呼び出し (回転後も含む) では同じインスタンスが返されます。ViewModel のクリアは onCleared() が呼び出されたときに自動的に行われます。このメソッドは、Activity が終了 (finish()) したとき、または Fragment が完全に削除されたときに呼び出されます。開発者は onCleared() をオーバーライドして、Flow の購読解除、コルーチンのキャンセル、ソケットのクローズなどのリソース解放を行うことができます。
Google は Jetpack のドキュメントで強調しています。ViewModel 内に Activity や View への参照を決して保存しないでください。ViewModel は UI を持つ Activity よりも長生きするため、メモリリークの原因になります。代わりに、LiveData、StateFlow、または SavedStateHandle を使用して ViewModel と UI 間でデータを渡してください。
MVVM (Model-View-ViewModel) パターンでは、ViewModel は View (Activity/Fragment) と Model (リポジトリ、DB、API) の間で中心的な位置を占めます。View は ViewModel (LiveData、StateFlow) のリアクティブデータを購読し、それらが変更されると自動的に更新されます。ViewModel は View の存在を知りません。データとコマンドを提供するだけで、View がそれらをどのように表示するかを決定します。
MVP と MVVM の比較:MVP では、Presenter が View (インターフェース) のメソッドを直接呼び出し、強結合を生み出します。MVVM では、ViewModel がリアクティブなデータストリームを公開し、View がそれを購読します。結合は一方向でテスト可能です。JetBrains Developer Survey (2024) によると、68% の Android 開発者が MVVM を主要アーキテクチャとして使用しており、ViewModel はこのパターンの主要コンポーネントです。
IT Sectr では、2018 年からすべての商用 Kotlin プロジェクトで ViewModel を使用した MVVM を採用しています。実践によると、このアプローチは責任の明確な分離と、エミュレータなしでのビジネスロジックのテスト容易性により、UI ロジックのデバッグ時間を 30~40% 削減します。
ViewModelProvider は、フラグメントまたは Activity で ViewModel を取得する標準的な方法です。デフォルトでは、ViewModelProvider は空のコンストラクタ (引数なし) を介して ViewModel を作成します。ViewModel がパラメータ (リポジトリやアプリケーションコンテキストなど) を必要とする場合は、ViewModelProvider.Factory を実装する必要があります。
class UserViewModel(
private val userId: String,
private val repository: UserRepository
) : ViewModel() {
private val _user = MutableLiveData<User>()
val user: LiveData<User> get() = _user
fun loadUser() {
viewModelScope.launch {
_user.value = repository.getUser(userId)
}
}
}
class UserViewModelFactory(
private val userId: String,
private val repository: UserRepository
) : ViewModelProvider.Factory {
override fun create<T : ViewModel>(modelClass: Class<T>): T {
return UserViewModel(userId, repository) as T
}
}
ファクトリーは、Fragment または Activity から ViewModel を取得する際に ViewModelProvider に渡されます。SavedStateHandle は AndroidX 1.2.0 で導入された代替のパラメータ受け渡しメカニズムです。ViewModel はコンストラクタを介して自動的に SavedStateHandle を受け取り、カスタムファクトリーを記述することなく Bundle を介して引数が渡されます。
viewModelScope は ViewModel に組み込まれ、そのライフサイクルにバインドされた CoroutineScope です。viewModelScope で起動されたすべてのコルーチンは、onCleared() が呼び出されたときに自動的にキャンセルされ、ViewModel 破棄後のメモリリークやバックグラウンド操作を防ぎます。
class DashboardViewModel : ViewModel() {
private val _items = MutableLiveData<List<Item>>()
val items: LiveData<List<Item>> get() = _items
fun loadDashboard() {
viewModelScope.launch(Dispatchers.IO) {
val result = repository.fetchDashboard()
withContext(Dispatchers.Main) {
_items.value = result
}
}
}
override fun onCleared() {
super.onCleared()
// すべての viewModelScope コルーチンは自動的にキャンセルされます
}
}
viewModelScope のコルーチンはデフォルトで Dispatchers.Main で実行されます。ネットワークやディスク操作の場合は、withContext を使用して Dispatchers.IO に切り替えるか、launch でディスパッチャを指定します。Google (Android Dev Summit 2024) によると、viewModelScope を使用すると、手動の Job 管理と比較してコルーチン関連のメモリリークが 95% 削減されます。
Hilt は、Dagger 上に構築された Android 向け Google 公式の依存性注入ライブラリです。Hilt を使用すると、ViewModelProvider.Factory を手動で記述する必要はありません。ViewModel コンストラクタに @HiltViewModel アノテーションを付けるだけです。Hilt は自動的にファクトリーを作成し、コンストラクタで宣言された依存関係を注入します。
@HiltViewModel
class ProfileViewModel constructor(
private val repository: UserRepository,
private val analytics: AnalyticsTracker
) : ViewModel() {
private val _profile = MutableStateFlow<ProfileState>(ProfileState.Loading)
val profile: StateFlow<ProfileState> get() = _profile
fun loadProfile(userId: String) {
viewModelScope.launch {
_profile.value = ProfileState.Success(repository.getUser(userId))
analytics.logEvent("profile_loaded")
}
}
}
// Fragment 内 — ファクトリーなし:
val viewModel: ProfileViewModel = by viewModels()
Koin はコード生成なしの代替 DI ライブラリです。Koin では、ViewModel は viewModel { } を介してモジュールで宣言され、フラグメントでは by viewModel() を介して取得されます。Hilt と Koin の選択はプロジェクトに依存します。Hilt はコンパイル時の依存関係グラフ検証を提供し、Koin はより軽量で kapt/ksp を必要としません。IT Sectr では、大規模プロジェクト (50 画面以上) では Hilt を、中規模プロジェクトでは Koin を使用しています。
画面回転時にリセットされない整数カウンターを保存するシンプルな ViewModel です。MutableLiveData と LiveData の基本的な使用パターンを示します。
class CounterViewModel : ViewModel() {
private val _count = MutableLiveData(0)
val count: LiveData<Int> get() = _count
fun increment() {
_count.value = (_count.value ?: 0) + 1
}
fun reset() {
_count.value = 0
}
}
プロセスがシステムによって強制終了された場合でも状態を自動的に保持するために SavedStateHandle を使用する ViewModel です。SavedStateHandle は、アプリがバックグラウンドで最小化され終了したときにデータを保存する唯一のメカニズムです。
class FormViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val userName = savedStateHandle.getLiveData<String>("userName", "")
val email = savedStateHandle.getLiveData<String>("email", "")
fun saveName(name: String) {
savedStateHandle["userName"] = name
}
fun saveEmail(email: String) {
savedStateHandle["email"] = email
}
}
SavedStateHandle の LiveData は、最後の値を自動的に Bundle に保存します。プロセスの再作成時 (アプリの最小化と終了後など) には Bundle が復元され、LiveData は以前の値を受け取ります。Google のテストによると、SavedStateHandle は Bundle に最大 5 KB のデータを保存できます。これはテキストフィールド、ID、シリアル化された JSON オブジェクトに十分です。
よくある質問
ViewModel はプロセスの RAM にデータを保存します。シリアル化なしで即座に利用でき、複雑なオブジェクト (リスト、Bitmap、ネットワーク応答) に適しています。onSaveInstanceState() はデータを Bundle にシリアル化し (Android 12 以降、1 トランザクションあたり最大 1 MB)、単純なプリミティブ、String、Serializable/Parcelable にのみ適しています。ViewModel + SavedStateHandle は Google 推奨の組み合わせです。ViewModel は実行時データ用、SavedStateHandle はプロセス強制終了時の復元用です。
いいえ、システムはスコープが終了すると自動的に onCleared() を呼び出します。viewModelStore.clear() による手動クリアは、テストケース間のリークを防ぐためにテストでのみ必要です。本番コードでは、手動で clear() を決して呼び出さないでください。ViewModel のライフサイクルが破壊され、予測不能な UI 動作を引き起こす可能性があります。
はい、ViewModel は Jetpack Compose で viewModel() 関数を介して完全にサポートされています。Compose では、ViewModel は Composable スコープレベルで取得され、スコープを出ると自動的にクリアされます。MVVM の Compose 版は Unidirectional Data Flow (UDF) と呼ばれます。ViewModel が StateFlow を公開し、Composable 関数が collectAsState() を介して購読します。Reducer アプローチの Compose 版は、ViewModel を使用した MVI です。
Activity、Fragment、View、Context (Application を除く) への参照を保存することは 禁止 されています。ViewModel は UI コンテキストより長生きするため、メモリリークの原因になります。シリアル化された View の状態 (RecyclerView の位置など) は 保存しないでください。LayoutManager.onSaveInstanceState() を使用してください。大量のデータ (10 MB 以上) の保存は 避けてください。プロセスが最小化されると、SavedStateHandle なしではデータが失われます。
ViewModel はエミュレータなしで通常の Kotlin クラスとしてテストされます。インスタンスを作成し、メソッドを呼び出し、LiveData または StateFlow の状態を確認します。コルーチンのテストには、TestDispatcher を使用して kotlinx-coroutines-test の runTest を使用します。Hilt を使用した ViewModel の場合は、テストフラグメントで @HiltViewModelTest と hiltViewModel() を使用します。Google によると、単体テストはインストルメント化テストなしで ViewModel ロジックの 80~90% をカバーします。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。