ViewModel は、Android のモダンアーキテクチャにおいて UI とロジックを分離するための中核コンポーネントです。
特に MVVM(Model–View–ViewModel)構成では、設計の良し悪しを左右する非常に重要な存在となります。
本記事では、ViewModel の役割・ライフサイクル・正しい使い方・注意点を、誤解が生じやすい点を修正しながら解説します。
ViewModelとは何か
ViewModel は、UI に表示される状態(State)と、それを生成・更新するロジックを保持するクラスです。
主な目的は次の3点です。
- Activity / Fragment からロジックを分離する
- 構成変更(画面回転など)時に UI 状態を保持する
- UI に依存しない形でロジックを管理し、テストしやすくする
ViewModel 自体は View(Activity / Fragment)を知らない という点が重要です。
なぜ ViewModel が必要なのか
Activity / Fragment に直接ロジックを書く場合の問題点
- 画面回転などの構成変更で
onCreate()が再実行され、状態が失われやすい - UI とビジネスロジックが密結合になり、クラスが肥大化する
- テストが困難になる
- 再利用性が低い
ViewModel は、これらの問題を解決するために導入されました。
MVVM における ViewModel の役割
MVVM では、責務を以下のように分離します。
- View(Activity / Fragment)
表示とユーザー操作のみを担当 - ViewModel
UI 状態の管理とロジックを担当 - Model(Repository / UseCase 等)
データ取得や変換を担当
重要なのは依存関係の向きです。
- View → ViewModel を参照する
- ViewModel は View を一切参照しない
この一方向の依存が、保守性の高い設計を実現します。
ViewModel の基本的な構造(Kotlin)
class MainViewModel : ViewModel() {
private val _count = MutableLiveData(0)
val count: LiveData<Int> = _count
fun increment() {
_count.value = (_count.value ?: 0) + 1
}
}
ポイント
MutableLiveDataは ViewModel 内部に隠す- View には
LiveDataとして公開する - UI 状態の更新責務は ViewModel が持つ
このカプセル化は必須です。
View から ViewModel を使う方法
Activity の場合
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.count.observe(this) { value ->
textView.text = value.toString()
}
}
}
Fragment の場合
private val viewModel: MainViewModel by viewModels()
なお、activityViewModels() を使えば、複数 Fragment で同じ ViewModel を共有することも可能です。
この場合、ViewModel は Activity スコープで管理されます。
ViewModel のライフサイクル
ViewModel は ViewModelStoreOwner(Activity / Fragment / Navigation スコープなど)に紐づいて管理されます。
構成変更(画面回転など)の場合
- Activity / Fragment は破棄され、新しいインスタンスが生成される
- ただし ViewModelStore は保持される
- 同じスコープであれば、既存の ViewModel が再利用される
このため、画面回転時でも状態を保持できます。
完全にスコープが破棄された場合
- Activity が終了する
- Navigation のバックスタックから完全に外れる
このタイミングで ViewModel は破棄されます。
「Activity が destroy されたら必ず ViewModel も破棄される」というわけではなく、スコープが継続しているかどうかが判断基準になります。
非同期処理と ViewModel
ViewModel は、構成変更中でも生き続けるため、非同期処理の置き場所として適しています。
ただし重要なのは前提条件です。
- 構成変更で ViewModel が保持される場合
→ 非同期処理は継続できる - スコープが終了して ViewModel が破棄された場合
→ 処理は終了させる必要がある
viewModelScope と Coroutine
class MainViewModel : ViewModel() {
fun loadData() {
viewModelScope.launch {
val result = repository.fetchData()
_data.value = result
}
}
}
viewModelScope の特徴
- ViewModel のライフサイクルに紐づく CoroutineScope
- ViewModel がクリアされると自動でキャンセルされる
- メモリリークや不要な処理継続を防げる
ViewModel で Coroutine を使う場合、原則 viewModelScope を使用します。
LiveData と StateFlow の位置づけ
LiveData
- Android ライフサイクルに強く依存
- observe 時に自動でライフサイクル制御される
- XML + DataBinding と相性が良い
StateFlow
- Kotlin Coroutine 標準
- Flow ベースで柔軟性が高い
- Jetpack Compose と相性が良い
- ライフサイクル制御は自分で行う必要がある(
repeatOnLifecycle等)
実務的な指針
- Compose / Flow 中心 → StateFlow
- XML / 既存コード重視 → LiveData
どちらが「絶対的に正しい」わけではなく、UI層の設計に合わせて選択します。
ViewModel に書いてよいもの・避けるべきもの
書いてよいもの
- UI 状態(数値、リスト、ロード状態など)
- 入力検証ロジック
- ビジネスロジック
- Repository 呼び出し
- 非同期処理制御
避けるべきもの
- Activity / Fragment / View の参照
- Toast 表示や画面遷移などの UI 操作
findViewByIdなどの View 操作- Context(特に Activity Context)
※ 例外として、Application Context が必要な場合は AndroidViewModel を使う選択肢がありますが、乱用は避けるべきです。
UIイベント(Toast・Navigation)の扱い
Toast や画面遷移は 状態ではなくイベントです。
単純に LiveData<String> で通知すると、
- 再購読時(回転など)に同じイベントが再実行される
という問題が起こります。
そのため、
- SharedFlow
- Channel
- one-shot event パターン
などを使い、「一度きりの通知」として扱う設計が推奨されます。
ViewModel は「UI スコープ単位」で考える
ViewModel は Repository のように アプリ全体で使い回すものではありません。
- ViewModel → UI スコープ単位(画面、Activity、NavGraph など)
- Repository → データ単位(API、DB、キャッシュなど)
この責務分離を守ることで、設計は大幅に安定します。
まとめ
- ViewModel は UI 状態とロジックを管理するためのクラス
- 構成変更でもスコープが生きていれば状態を保持できる
- 非同期処理は viewModelScope で管理する
- View や Context を直接扱わない
- 状態とイベントを明確に分離する
- UI スコープ単位で ViewModel を設計する
以上、KotlinのViewModelについてでした。
最後までお読みいただき、ありがとうございました。










