viewModelScope: その概要、ViewModel との関連付け、Android での動作

著者: IT Sectr 公開日: 2026-06-23 読了時間: 9 分

viewModelScope は、androidx.lifecycle ライブラリの組み込み CoroutineScope で、ViewModel のライフサイクルに関連付けられ、クリア時に自動的にキャンセルされます。Google Android Developers(2025 年)によると、viewModelScope は MVVM アーキテクチャでコルーチンを起動する標準的なメカニズムであり、メモリリークのリスクなく安全な非同期操作を保証します。ViewModelScope はデフォルトで Dispatchers.Main を使用し、その内部のすべての IO 操作は withContext を介して実行する必要があります。

重要なポイント

  • viewModelScope — lifecycle-viewmodel-ktx の CoroutineScope。ViewModel.onCleared() でキャンセルされます
  • Dispatchers.Main — デフォルトのディスパッチャー。コルーチン内の UI 更新は安全です
  • onCleared — viewModelScope 内のすべてのアクティブなコルーチンを自動的にキャンセルするコールバック
  • clear() と onCleared() — clear() はフレームワークによって onCleared の前に呼び出され、スコープのキャンセルを保証します
  • launch — fire-and-forget 操作のために viewModelScope でコルーチンを開始する主要な方法

Android における viewModelScope とは?

viewModelScope は、ViewModel インターフェースの拡張プロパティであり、lifecycle-viewmodel-ktx ライブラリ(バージョン 2.1.0 以降)で追加されました。ViewModel のライフサイクルに関連付けられた、すぐに使用できる CoroutineScope を提供します。

kotlin
// Internal structure (simplified)
val ViewModel.viewModelScope: CoroutineScope
    get() {
        val scope = this.getTag(JOB_KEY)
        if (scope != null) return scope
        return CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
            .also { setTag(JOB_KEY, it) }
    }

スコープは最初のアクセス時に遅延(lazy)作成され、setTag を介してキャッシュされます。SupervisorJob を使用するため、1 つの子コルーチンでの例外が他のコルーチンをキャンセルすることはありません。デフォルトのディスパッチャーは Dispatchers.Main.immediate で、既に Main スレッド上にある場合は追加のディスパッチなしでメインスレッドでコードを実行します。

viewModelScope がクリア通知を受け取る仕組み

ViewModel がライフサイクルを離れると(Activity の終了または Fragment の削除)、システムは clear() を呼び出し、onCleared() をトリガーします。このコールバックで、viewModelScope は Job をキャンセルし、すべてのアクティブなコルーチンを再帰的に終了します。このメカニズムは Closeable インターフェースを介して実装され、スコープの Job は自動クローズのリソースとして登録されます。

viewModelScope の仕組み:ViewModel ライフサイクルとの関連付け

viewModelScope の ViewModel ライフサイクルへの関連付けメカニズムは、タグ付けと onCleared コールバックに基づいています。段階的に見ていきましょう。

ステップ 1:最初のアクセス時のスコープ作成

ViewModel が viewModelScope.launch { ... } を実行すると、getter は JOB_KEY タグの下にスコープが既に保存されているかどうかを確認します。スコープが存在しない場合、新しい CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) インスタンスが作成されます。スコープは内部タグマップを介して ViewModel 内に保存されます。

ステップ 2:コルーチンのライフサイクル

viewModelScope.launch または viewModelScope.async を介して起動されたすべてのコルーチンは、スコープの SupervisorJob の子になります。これらはメインスレッドで実行されます(withContext を介して別のディスパッチャーが指定されていない限り)。ViewModel が存続している限り、コルーチンはアクティブ、一時停止、または完了状態になります。

ステップ 3:onCleared でのキャンセル

システムが ViewModel を破棄すると、ViewModel.clear() が呼び出されます。clear() 内部では、次の処理が行われます:

  • カスタムクリーンアップロジックのために onCleared() が呼び出される
  • addCloseable を介して登録されたすべての Closeable リソースが閉じられる
  • viewModelScope の Job が Cancelled 状態に遷移する
  • すべての子コルーチンが再帰的にキャンセルされる
  • ガベージコレクションのためにスコープへの参照が解放される

画面回転への耐性

画面が回転すると Activity は再作成されますが、ViewModel は存続します(ViewModelStoreOwner のおかげです)。つまり、viewModelScope はアクティブなままで、コルーチンは中断されることなく実行を続けます。Activity の再作成後も、同じ ViewModel(および同じスコープ)が再利用されるため、データの読み込みが最初からやり直されることはありません。

MVVM アーキテクチャにおける viewModelScope

MVVM(Model-View-ViewModel)は、Google が推奨する Android アプリケーションのアーキテクチャです。viewModelScope は、非同期操作の実行者として中心的な役割を果たします。

アーキテクチャ層における viewModelScope の役割

コンポーネントviewModelScope の役割
UIActivity / FragmentViewModel からの StateFlow/LiveData を監視
ViewModelViewModelviewModelScope を介してコルーチンを起動、UI 状態を管理
RepositoryRepositoryviewModelScope のコルーチンから呼び出される suspend 関数を提供
DataDAO / Api実際のリクエストを実行(Room、Retrofit)

ViewModel は viewModelScope を介してコルーチンを起動し、その内部で Repository の suspend 関数 を呼び出します。結果は StateFlow に変換され、UI 層によって監視されます。この設計により、明確な責任の分離と各層の独立したテスト容易性が保証されます。

なぜ ViewModel で viewModelScope を使うのか、Fragment ではないのか

コルーチンが Fragment から起動された場合、画面回転時に Fragment の破棄とともにキャンセルされます。ViewModel は回転後も存続するため、そのスコープで起動されたコルーチンは実行を継続します。これが、データ読み込み時における lifecycleScope に対する viewModelScope の主な利点です。

viewModelScope の使用例

Kotlin を使用した Android アプリケーションにおける viewModelScope の 3 つの実践的なシナリオを見てみましょう。

例 1:ViewModel 作成時のデータ読み込み

kotlin
class ProfileViewModel(
    private val repo: ProfileRepository
) : ViewModel() {

    private val _profile = MutableStateFlow<Profile?>(null)
    val profile: StateFlow<Profile?> = _profile

    init {
        loadProfile()
    }

    private fun loadProfile() {
        viewModelScope.launch {
            val result = repo.getProfile()
            _profile.value = result
        }
    }
}

init ブロックで、プロフィールの読み込みが直ちに開始されます。コルーチンはデフォルトでメインスレッドで実行されます。リポジトリは suspend 関数内でネットワークリクエストに withContext(Dispatchers.IO) を使用するため、ViewModel はスレッド切り替えを気にする必要がありません。

例 2:sealed class を使用したエラー処理

kotlin
sealed class UiState {
    object Loading : UiState()
    data class Success(val data: List<Item>) : UiState()
    data class Error(val message: String) : UiState()
}
kotlin
fun fetchItems() {
    _state.value = UiState.Loading
    viewModelScope.launch {
        try {
            val items = repo.getItems()
            _state.value = UiState.Success(items)
        } catch (e: Exception) {
            _state.value = UiState.Error(e.message ?: "Unknown error")
        }
    }
}

UI の状態は sealed class UiState を使用して記述されます。ViewModel は変更のたびに状態を更新します。Fragment は StateFlow に購読し、現在の状態にのみ反応し、以前の回転からの古い呼び出しを無視します。

例 3:新しいリクエスト時の以前のコルーチンのキャンセル

kotlin
private var searchJob: Job? = null

fun search(query: String) {
    searchJob?.cancel()
    searchJob = viewModelScope.launch {
        delay(300)
        val results = repo.search(query)
        _searchResults.value = results
    }
}

新しい検索クエリごとに、以前のコルーチンがキャンセルされます。delay(300) はデバウンスを実装し、300 ミリ秒の非アクティブ後にのみ検索が実行されます。これによりサーバーの負荷が軽減され、古い結果が防止されます。

viewModelScope と lifecycleScope:どちらを選ぶべきか

どちらのスコープも AndroidX Lifecycle ライブラリによって提供されますが、異なるライフサイクルに関連付けられています。選択はタスクの種類によって異なります。

スコープの比較

特性viewModelScopelifecycleScope
所有者ViewModelLifecycleOwner(Activity/Fragment)
回転時にキャンセルいいえ(ViewModel は存続)はい(Activity は再作成)
デフォルトディスパッチャーDispatchers.Main.immediateDispatchers.Main.immediate
利用可能場所ViewModelActivity、Fragment、Service
典型的な使用例データ読み込み、ビジネスロジックUI 操作、アニメーション

Google の推奨事項

Google は、すべてのデータ読み込みおよび処理タスクに viewModelScope を使用することを推奨しています。lifecycleScope は、特定の UI ライフサイクルの瞬間に関連付けられた操作(初回画面表示時のアニメーション開始や、画面離脱時に停止すべき位置情報更新の購読など)に使用する必要があります。

viewModelScope を使用する際のよくある間違い

十分に文書化された Android API であっても、開発者は特有の間違いを犯します。最も一般的な 4 つの問題を見てみましょう。

間違い 1:スコープキャンセル後の UI 更新

最も厄介な間違いは、ViewModel がクリアされた後に StateFlow や LiveData を更新しようとすることです。viewModelScope は onCleared() でキャンセルされますが、コルーチンは実際のキャンセルが有効になる前にコードを実行する可能性があります。確認には isActive を使用するか、catch ブロックの完了に依存してください。

間違い 2:SupervisorJob を考慮せずにコルーチンを起動する

viewModelScope は内部的に SupervisorJob を使用して、コルーチン間のエラーを分離します。ただし、viewModelScope.launch 内で独自の Job() を持つコルーチンを起動した場合、そのコルーチンは SupervisorJob の子になりますが、他のコルーチンのエラーによるキャンセルからは保護されません。

間違い 3:1 つのスコープに多数のコルーチン

viewModelScope に厳密な制限はありませんが、数千のアクティブなコルーチンはシステムを遅くする可能性があります。長いデータリストの場合は、各アイテムに個別のコルーチンを作成する代わりに、Flow を collectLatest とともに使用してください。

間違い 4:viewModelScope の代わりに GlobalScope を使用する

viewModelScope の代わりに誤って GlobalScope をインポートした場合、ViewModel のクリア時にコルーチンはキャンセルされません。これによりメモリリークや潜在的なクラッシュが発生します。特に Fragment のサブクラスでは、常にコルーチンが viewModelScope を介して起動されていることを確認してください。

よくある質問

viewModelScope のデフォルトディスパッチャーを変更できますか?

viewModelScope のディスパッチャーを直接変更することはできません。Dispatchers.Main.immediate としてハードコードされています。ただし、コルーチン内で withContext を介して別のディスパッチャーに切り替えることは可能です。テストでディスパッチャーを変更するには、Rule を介して TestDispatcher を使用してください。

viewModelScope を Repository に渡すにはどうすればよいですか?

スコープを Repository に渡さないでください。アーキテクチャの原則に反します。Repository は suspend 関数 を公開し、ViewModel 自体が viewModelScope を介してコルーチンを管理する必要があります。Repository がスコープを必要とする場合は、Clean Architecture を採用してアーキテクチャを再検討してください。

viewModelScope が SupervisorJob を使用する理由は?

SupervisorJob は、1 つのコルーチンでの例外(複数の独立したリクエストのうち 1 つでの読み込みエラーなど)が他のコルーチンをキャンセルしないことを保証します。これは、異なる画面が独立したデータを読み込む ViewModel のシナリオに適合します。

viewModelScope は Jetpack Compose で利用できますか?

はい、viewModelScope は UI の種類(View System または Jetpack Compose)に関係なく、任意の ViewModel で利用できます。Compose でもコルーチンは viewModelScope を介して起動され、UI エフェクトには LaunchedEffectrememberCoroutineScope が使用されます。

viewModelScope.cancel() を呼び出すとコルーチンはどうなりますか?

viewModelScope.cancel() を呼び出すと、スコープは直ちにキャンセルされ、すべてのアクティブなコルーチンが CancellationException で終了します。その後 viewModelScope.launch を呼び出すと、getter への次回アクセス時に新しいスコープが自動的に作成されます。

まとめ

  • viewModelScope — ViewModel のライフサイクルに関連付けられた CoroutineScope。onCleared() で自動キャンセル
  • SupervisorJob + Dispatchers.Main — エラー分離と安全な UI アクセスを保証する内部構成
  • 画面回転 — ViewModel は存続するため、viewModelScope のコルーチンは再起動なしで継続
  • MVVM アーキテクチャ — viewModelScope は ViewModel 層での非同期操作の中心要素
  • lifecycleScope — ViewModel ではなく Activity/Fragment のライフサイクルに関連付けられた操作の代替手段
  • StateFlow — sealed class を介して viewModelScope のコルーチンから UI にデータを渡す推奨方法
  • GlobalScope は危険 — viewModelScope を GlobalScope に置き換えるとメモリリークやアプリのクラッシュを引き起こす

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください