viewModelScope: what it is, binding to ViewModel and how it works in Android

Author: IT Sectr Published: 2026-06-23 Reading time: 9 min

viewModelScope is a built-in CoroutineScope from the androidx.lifecycle library that is tied to the ViewModel lifecycle and automatically cancelled when the ViewModel is cleared. According to Google Android Developers, 2025, viewModelScope is the standard mechanism for launching coroutines in MVVM architecture, ensuring safe asynchronous operations without the risk of memory leaks. ViewModelScope uses Dispatchers.Main by default, and all IO operations within it must be executed via withContext.

Key Takeaways

  • viewModelScope — CoroutineScope from lifecycle-viewmodel-ktx, cancelled when ViewModel.onCleared() is called
  • Dispatchers.Main — default dispatcher, so UI updates inside coroutines are safe
  • onCleared — callback that triggers automatic cancellation of all active coroutines in viewModelScope
  • clear() vs onCleared() — clear() is called by the framework before onCleared, guaranteeing scope cancellation
  • launch — the primary way to start coroutines in viewModelScope for fire-and-forget operations

What is viewModelScope in Android?

viewModelScope is an extension property on the ViewModel interface, added in the lifecycle-viewmodel-ktx library (starting from version 2.1.0). It provides a ready-to-use CoroutineScope tied to the ViewModel lifecycle.

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) }
    }

The scope is created lazily on first access and cached via setTag. It uses SupervisorJob, which means an exception in one child coroutine does not cancel the others. The default dispatcher is Dispatchers.Main.immediate, which executes code on the main thread without additional dispatching if already on the Main thread.

How viewModelScope gets notified about clearing

When the ViewModel leaves the lifecycle (Activity is finished or Fragment is removed), the system calls clear(), which triggers onCleared(). In this callback, viewModelScope cancels its Job, which recursively terminates all active coroutines. The mechanism is implemented through the Closeable interface, where the scope Job is registered as a resource for automatic closing.

How viewModelScope works: binding to the ViewModel lifecycle

The mechanism of binding viewModelScope to the ViewModel lifecycle is based on tagging and the onCleared callback. Let us go through it step by step.

Step 1: Scope creation on first access

When the ViewModel calls viewModelScope.launch { ... }, the getter checks if a scope is already stored under the JOB_KEY tag. If no scope exists, a new CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) instance is created. The scope is stored inside the ViewModel via an internal tag map.

Step 2: Coroutine lifecycle

All coroutines launched via viewModelScope.launch or viewModelScope.async become children of the scope’s SupervisorJob. They run on the main thread (unless a different dispatcher is specified via withContext). As long as the ViewModel is alive, coroutines can be active, suspended, or completed.

Step 3: Cancellation on onCleared

When the system destroys the ViewModel, ViewModel.clear() is called. Inside clear(), the following happens:

  • onCleared() is called for custom cleanup logic
  • All Closeable resources registered via addCloseable are closed
  • The viewModelScope Job transitions to the Cancelled state
  • All child coroutines are recursively cancelled
  • References to the scope are released for garbage collection

Rotation resilience

When the screen is rotated, the Activity is recreated, but the ViewModel survives (thanks to ViewModelStoreOwner). This means viewModelScope remains active and coroutines continue executing without interruption. After the Activity is recreated, the same ViewModel (and the same scope) is reused — data loading does not start from scratch.

viewModelScope in MVVM architecture

MVVM (Model-View-ViewModel) is Google’s recommended architecture for Android applications. viewModelScope plays a central role in it as the executor of asynchronous operations.

The role of viewModelScope in architecture layers

LayerComponentviewModelScope role
UIActivity / FragmentObserves StateFlow/LiveData from ViewModel
ViewModelViewModelLaunches coroutines via viewModelScope, manages UI state
RepositoryRepositoryExposes suspend functions called from viewModelScope coroutines
DataDAO / ApiExecutes actual requests (Room, Retrofit)

The ViewModel launches coroutines via viewModelScope, inside which it calls suspend functions of the Repository. The result is transformed into StateFlow, which is observed by the UI layer. This design ensures clear separation of concerns and independent testability of each layer.

Why viewModelScope in ViewModel and not in Fragment

If coroutines were launched from a Fragment, they would be cancelled on screen rotation along with the Fragment destruction. ViewModel survives rotation, so coroutines launched in its scope continue execution. This is the key advantage of viewModelScope over lifecycleScope when loading data.

viewModelScope usage examples

Let us look at three practical scenarios for using viewModelScope in an Android application with Kotlin.

Example 1: Loading data on ViewModel creation

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
        }
    }
}

In the init block, profile loading starts immediately. The coroutine runs on the main thread (by default). The repository uses withContext(Dispatchers.IO) for the network request inside its suspend function, so the ViewModel does not need to handle thread switching.

Example 2: Error handling with 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 state is described using a sealed class UiState. The ViewModel updates the state on each change. The Fragment subscribes to StateFlow and reacts only to the current state, ignoring stale calls from previous rotations.

Example 3: Cancelling the previous coroutine on a new request

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
    }
}

On each new search query, the previous coroutine is cancelled. delay(300) implements debounce — the search executes only after 300 ms of inactivity. This reduces server load and prevents stale results.

viewModelScope vs lifecycleScope: when to choose which

Both scopes are provided by the AndroidX Lifecycle library but are tied to different lifecycles. The choice depends on the type of task.

Scope comparison

CharacteristicviewModelScopelifecycleScope
OwnerViewModelLifecycleOwner (Activity/Fragment)
Cancelled on rotationNo (ViewModel survives)Yes (Activity is recreated)
Default dispatcherDispatchers.Main.immediateDispatchers.Main.immediate
Available inViewModelActivity, Fragment, Service
Typical use caseData loading, business logicUI interactions, animations, chunks

Google recommendations

Google recommends using viewModelScope for all data loading and processing tasks. lifecycleScope should be used for operations tied to a specific UI lifecycle moment — for example, starting an animation on first screen appearance or subscribing to Location updates that should stop when leaving the screen.

Common mistakes when working with viewModelScope

Even in a well-documented Android API, developers make typical mistakes. Let us look at four of the most common issues.

Mistake 1: Updating UI after scope cancellation

The most insidious mistake is trying to update StateFlow or LiveData after the ViewModel has been cleared. Although viewModelScope is cancelled on onCleared(), a coroutine may execute code before the actual cancellation takes effect. Use isActive to check or rely on catch-block completion.

Mistake 2: Launching coroutines without considering SupervisorJob

viewModelScope uses SupervisorJob internally, which isolates errors between coroutines. However, if you launch a coroutine with its own Job() inside viewModelScope.launch, that coroutine becomes a child of SupervisorJob but will not be protected from cancellation caused by errors in other coroutines.

Mistake 3: Too many coroutines in one scope

Although viewModelScope has no hard limit, thousands of active coroutines can slow down the system. For long data lists, use Flow with collectLatest instead of creating separate coroutines for each item.

Mistake 4: Using GlobalScope instead of viewModelScope

If GlobalScope is accidentally imported instead of viewModelScope, the coroutine will not be cancelled when the ViewModel is cleared. This leads to memory leaks and potential crashes. Always ensure coroutines are launched via viewModelScope, especially in Fragment subclasses.

Frequently Asked Questions

Can I change the default dispatcher of viewModelScope?

You cannot directly change the dispatcher of viewModelScope — it is hardcoded to Dispatchers.Main.immediate. However, inside a coroutine you can switch to another dispatcher via withContext. To change the dispatcher in tests, use TestDispatcher via a Rule.

How do I pass viewModelScope to Repository?

Do not pass scope to the Repository — this breaks architectural principles. The Repository should expose suspend functions, and the ViewModel itself manages coroutines through viewModelScope. If the Repository requires a scope, reconsider the architecture in favor of Clean Architecture.

Why does viewModelScope use SupervisorJob?

SupervisorJob ensures that an exception in one coroutine (e.g., a loading error in one of several independent requests) does not cancel the other coroutines. This matches the ViewModel scenario where different screens load independent data.

Is viewModelScope available in Jetpack Compose?

Yes, viewModelScope is available in any ViewModel regardless of the UI type (View System or Jetpack Compose). In Compose, coroutines are also launched via viewModelScope, while UI effects use LaunchedEffect and rememberCoroutineScope.

What happens to a coroutine when viewModelScope.cancel() is called?

Calling viewModelScope.cancel() cancels the scope immediately — all active coroutines terminate with CancellationException. If viewModelScope.launch is called afterwards, a new scope is created automatically on the next access to the getter.

Summary

  • viewModelScope — CoroutineScope tied to the ViewModel lifecycle, automatically cancelled on onCleared()
  • SupervisorJob + Dispatchers.Main — internal configuration ensuring error isolation and safe UI access
  • Screen rotation — ViewModel survives, so coroutines in viewModelScope continue without restart
  • MVVM architecture — viewModelScope is the central element for asynchronous operations in the ViewModel layer
  • lifecycleScope — alternative for operations tied to Activity/Fragment lifecycle, not ViewModel
  • StateFlow — preferred way to pass data from viewModelScope coroutines to UI via sealed class
  • GlobalScope is dangerous — replacing viewModelScope with GlobalScope leads to memory leaks and app crashes

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also