← Back to android roadmap

How a ViewModel Survives Rotation, in Activities and in Compose

What a ViewModel is, who owns it, how the store is handed from the old Activity to the new one, when onCleared() really runs, and how Fragments, Navigation and Compose find the right instance. Read from the AndroidX source.

  • Android
  • ViewModel
  • Jetpack Compose
  • Internals
On this page
  1. What you’ll study
  2. The basics
  3. The high level
  4. Checkpoint
  5. The low level
  6. Why it helps
  7. The map
  8. Edge cases
  9. Cross questions
  10. Revision: key points
  11. The fine print

What you’ll study

  1. What recreation is, and what a ViewModel does and doesn’t survive.
  2. Owner → store → ViewModel, and get-or-create by key.
  3. The hand-off on rotation (NonConfigurationInstances).
  4. When onCleared() runs, and how viewModelScope is built.
  5. Owners in Fragments, Navigation and Compose.
  6. Leaks, sharing between screens, and process death.

How it connects

A ViewModel isn’t special by itself; it survives because of where it is stored. Every owner has a store, a map of instances, and asking for a ViewModel looks it up there: same store, same instance. Rotation hands the same store to the new Activity in memory; a real finish clears it. Fragments, Navigation and Compose only change which owner, and so which store, you get. And because the hand-off happens in memory, process death ends it.

Read it at your level

Level Read You should be able to
Student The basics, the high level, the demo Explain why a ViewModel survives rotation and a field doesn’t
Fresher + the Compose section Use by viewModels() and viewModel() correctly, and load data in init
Junior + Why it helps, cross questions Avoid Context leaks, share a ViewModel between fragments, scope one per screen
Mid + The low level, The map Explain the store hand-off, the clear rule, viewModelScope and CreationExtras
Senior + Edge cases Reason about process death, configChanges, recreate(), Android 12 Back and Compose owners

The basics

When the user rotates the phone, switches to dark mode or resizes a window, Android destroys the Activity and creates a new one with the new configuration. Every field in the old Activity is gone. If the screen was halfway through loading a list, that work and its result go with it.

A ViewModel is an object that holds a screen’s state and logic and outlives that recreation. The new Activity gets back the same ViewModel instance, with its loaded list and running coroutines intact.

class ProfileViewModel(private val repo: ProfileRepo) : ViewModel() {
    private val _ui = MutableStateFlow<ProfileUi>(ProfileUi.Loading)
    val ui: StateFlow<ProfileUi> = _ui

    init {
        viewModelScope.launch { _ui.value = repo.load() }
    }
}

// Activity or Fragment
private val vm: ProfileViewModel by viewModels { factory }

// Compose
@Composable
fun ProfileScreen(vm: ProfileViewModel = viewModel()) { /* … */ }

What a ViewModel does not survive: the user finishing the screen (it is cleared), and the system killing the process while the app is in the background (everything in memory is gone). The second case is the subject of the next guide.

The high level

The whole mechanism is three objects:

ViewModelStoreOwner        "something with a lifetime that can own ViewModels"
  │                        Activity, Fragment, NavBackStackEntry, …
  │ has one
  ▼
ViewModelStore             a map: key ─► ViewModel instance
  │
  ▼
ViewModel                  your state + viewModelScope

and two rules:

  1. Get-or-create by key. Asking for a ViewModel looks up its key in the owner’s store. If it’s there, you get the same instance. If not, a factory creates one and stores it.
  2. The store outlives a configuration change, not a finish. When an Activity is destroyed only to be recreated, its store is handed to the new instance. When it is destroyed for good, the store is cleared and every ViewModel in it gets onCleared().
Rotation  (isChangingConfigurations = true)
Activity#1 ──destroy──┐
   store ─────────────┴──► Activity#2   same store, same ViewModels

Back / finish  (isChangingConfigurations = false)
Activity#1 ──destroy──► store.clear() ──► onCleared() on each ViewModel

Interactive demo: an Activity with screens on a back stack and their ViewModelStores. Rotate, navigate, go back, finish or let the system kill the process, and watch which ViewModel instances survive and when onCleared() runs. Needs JavaScript.

Checkpoint

Before going deeper, you should be able to say:

  • A ViewModel lives in its owner’s ViewModelStore, keyed by its class (or your key).
  • Rotation reuses the store; a real finish clears it.
  • Nothing is serialised: survival is in memory only, so process death ends it.

Try rotate, open detail, back and process death in the demo before reading the code.

The low level

1. by viewModels() → ViewModelProvider.get()

by viewModels() (and Compose’s viewModel()) end up in ViewModelProviderImpl.getViewModel:

// simplified from ViewModelProviderImpl.kt
fun <T : ViewModel> getViewModel(modelClass: KClass<T>, key: String): T =
    synchronized(lock) {
        val existing = store[key]
        if (modelClass.isInstance(existing)) return existing as T
        val extras = MutableCreationExtras(defaultExtras)
        extras[VIEW_MODEL_KEY] = key
        factory.create(modelClass, extras).also { store.put(key, it) }
    }
  • The default key is "androidx.lifecycle.ViewModelProvider.DefaultKey:" plus the class name. So one owner holds one instance per class, unless you pass your own key.
  • ViewModelStore.put() replaces any old instance under that key, and clears the old one.
  • The factory receives CreationExtras. ComponentActivity fills them with the Application, itself as SavedStateRegistryOwner and ViewModelStoreOwner, and the intent extras as default arguments. That is how createSavedStateHandle() and Hilt’s factory get what they need without a reference to the Activity.

2. The hand-off on rotation

This is the part most people never see. In ComponentActivity:

// simplified from ComponentActivity.kt
internal class NonConfigurationInstances {
    var custom: Any? = null
    var viewModelStore: ViewModelStore? = null
}

final override fun onRetainNonConfigurationInstance(): Any? {
    val nci = NonConfigurationInstances()
    nci.viewModelStore = _viewModelStore
        ?: (lastNonConfigurationInstance as? NonConfigurationInstances)
            ?.viewModelStore
    return nci
}
  1. During a configuration change the platform calls onRetainNonConfigurationInstance() on the dying Activity and keeps the returned object in memory, outside the Activity.
  2. The new Activity’s viewModelStore getter calls ensureViewModelStore(), which reads lastNonConfigurationInstance and reuses that store instead of making a new one.
  3. Nothing is serialised. It’s the same object in the same process, which is why any type can live in a ViewModel, and why none of it survives process death.

onRetainNonConfigurationInstance() is final. That’s deliberate: AndroidX owns this hook now.

3. When onCleared() runs

ComponentActivity observes its own lifecycle:

// from ComponentActivity.kt
if (event == Lifecycle.Event.ON_DESTROY) {
    if (!isChangingConfigurations) {
        viewModelStore.clear()
    }
}

ViewModelStore.clear() calls clear() on every ViewModel, which:

  1. closes every AutoCloseable registered with addCloseable(), including viewModelScope, so its coroutines are cancelled;
  2. then calls your onCleared().

viewModelScope itself is created lazily on first use with a SupervisorJob (one failing child doesn’t cancel the others) and Dispatchers.Main.immediate (it runs on the main thread, without an extra hop when you’re already on it).

4. Fragments

A Fragment’s ViewModels live inside a FragmentManagerViewModel, which is itself a ViewModel in the Activity’s store. It keeps one ViewModelStore per fragment, keyed by the fragment’s internal id, plus one per child FragmentManager.

  • When the Activity rotates, the FragmentManagerViewModel survives with it, so every fragment’s store survives too.
  • When a fragment is removed for good (not just put on the back stack), its store is cleared.
  • by activityViewModels() asks the Activity’s store instead: that’s how two fragments share one instance.

5. Navigation

Navigation gives every back-stack entry its own store. NavBackStackEntry is a ViewModelStoreOwner, and its stores live in a NavViewModelStoreProvider held in the host’s store, keyed by the entry’s id.

  • A destination’s ViewModel lives as long as its entry is on the back stack, and is cleared when the entry is popped.
  • To share one between screens, scope it to a parent graph’s entry (navGraphViewModels(R.id.flow), or viewModel(navController.getBackStackEntry("flow")) in Compose).

6. Compose

viewModel() is the same get-or-create, with the owner taken from a CompositionLocal:

// from lifecycle-viewmodel-compose
inline fun <reified VM : ViewModel> viewModel(
    viewModelStoreOwner: ViewModelStoreOwner =
        checkNotNull(LocalViewModelStoreOwner.current) { … },
    key: String? = null,
    factory: ViewModelProvider.Factory? = null,
    extras: CreationExtras =
        viewModelStoreOwner.defaultViewModelCreationExtras,
): VM
  • LocalViewModelStoreOwner.current falls back to the View tree’s owner, which is the hosting Activity or Fragment. In a plain setContent { }, every viewModel<Foo>() call in the whole screen returns the same instance, scoped to the Activity.
  • Inside NavHost, each destination provides its NavBackStackEntry as the owner, so each screen gets its own instance, cleared when the screen is popped.
  • A composable leaving the composition does not clear a ViewModel. Only its owner can. Newer lifecycle versions add rememberViewModelStoreOwner() for ViewModels scoped to a single composable; check your version before you rely on it.

Why it helps

  • “Data reloads on every rotation.” You created state in the Activity or in remember, not in the ViewModel, or you started the load in onStart(). Load in init (or lazily from a flow) so it happens once per ViewModel, not once per Activity.
  • “Memory leak after rotation.” The ViewModel outlives the Activity, so a reference to an Activity, View or Fragment from it keeps the dead one alive. Pass the Application (through AndroidViewModel or CreationExtras) if you need a Context.
  • “Two screens got the same ViewModel.” In Compose without Navigation, everything shares the Activity’s owner. Use per-destination entries or a key.
  • “State lost after the app was in the background.” Process death. The ViewModel was never meant to survive it. Put what the user would miss in SavedStateHandle, and real data in a database.
  • “My cleanup in onCleared() didn’t run.” It only runs on a real finish. On process death, nothing runs at all.

The map

Idea Where it lives
Owner of ViewModels ViewModelStoreOwner (ComponentActivity, Fragment, NavBackStackEntry)
The map of instances ViewModelStore (put clears a replaced instance, clear clears all)
Get-or-create ViewModelProvider, ViewModelProviderImpl.getViewModel, default key
Creating with dependencies ViewModelProvider.Factory, CreationExtras, viewModelFactory { }
Hand-off on rotation ComponentActivity.onRetainNonConfigurationInstance, NonConfigurationInstances, ensureViewModelStore
Clearing on finish ComponentActivity ON_DESTROY observer, isChangingConfigurations
Coroutines viewModelScope (SupervisorJob + Dispatchers.Main.immediate), addCloseable
Fragments FragmentManagerViewModel, by viewModels(), by activityViewModels()
Navigation NavBackStackEntry, NavViewModelStoreProvider, navGraphViewModels
Compose viewModel(), LocalViewModelStoreOwner, rememberViewModelStoreOwner()
Saved state from a ViewModel SavedStateHandle, createSavedStateHandle()

Edge cases

  • Process death. The ViewModel, its store and everything in memory are gone. The Activity is recreated from its saved Bundle, and a new ViewModel is created. Only SavedStateHandle values come back.
  • android:configChanges in the manifest. If you declare a change yourself, the Activity isn’t recreated for it: onConfigurationChanged() is called instead, and no hand-off happens.
  • recreate(). Recreates the Activity like a configuration change, so ViewModels survive.
  • Back on a root Activity (Android 12+). For the launcher Activity, Back moves the task to the background instead of finishing it, so its ViewModels are kept.
  • Asking too early. Reading viewModelStore before the Activity is attached throws “You can’t request ViewModel before onCreate call.”
  • Fragment on the back stack. Its view is destroyed but the fragment isn’t, so its ViewModel survives until the fragment is popped.
  • Same class, two instances. Pass different keys, or use different owners.

Cross questions

Where exactly is the ViewModel kept during rotation, if the Activity is destroyed?

In the NonConfigurationInstances object that ComponentActivity.onRetainNonConfigurationInstance() returns. The platform holds that object across the relaunch and hands it to the new Activity as lastNonConfigurationInstance, where ensureViewModelStore() picks up the same ViewModelStore.

Why doesn't a ViewModel survive process death if it survives rotation?

Rotation keeps the same object in memory in the same process. Process death ends the process, so there is no memory to keep it in. Only data the system can store outside the process (the saved Bundle) or data on disk survives.

How does the ViewModel know whether to clear itself on onDestroy()?

It doesn't; the owner decides. ComponentActivity clears its store at ON_DESTROY only when isChangingConfigurations is false. Fragments and Navigation entries clear their stores when they are removed for good.

What runs first when a ViewModel is cleared, viewModelScope cancellation or onCleared()?

The scope. clear() closes every registered AutoCloseable, including viewModelScope, before calling onCleared(). Don't launch new work on viewModelScope from onCleared(); it is already cancelled.

Two fragments in one Activity need to share data. How?

Scope a ViewModel to the Activity with by activityViewModels(), or to a navigation graph with navGraphViewModels(), so both fragments get the same instance from the same store.

In Compose, why did two different screens get the same ViewModel?

Without Navigation, LocalViewModelStoreOwner is the Activity for the whole tree, and the default key is the class name. Use Navigation (each destination is its own owner), pass a key, or provide a narrower owner.

Why shouldn't a ViewModel hold a Context?

It outlives the Activity. A reference to an Activity, View or Fragment keeps the destroyed one in memory after every rotation. Use the Application context, which lives as long as the process.

What is CreationExtras for?

It lets a factory create a ViewModel with what it needs (the Application, saved-state owner, default arguments, the key) without holding the owner. createSavedStateHandle() reads its inputs from there.

Is viewModelScope on the main thread? Is that a problem?

Yes, Dispatchers.Main.immediate. It's fine for updating state; switch with withContext(Dispatchers.IO) (or call suspend functions that do) for blocking work.

Revision: key points

  1. A ViewModel holds state and logic that outlive a configuration change.
  2. Owner → ViewModelStore (a map) → ViewModel; the default key is the class name.
  3. On rotation, onRetainNonConfigurationInstance() hands the same store to the new Activity.
  4. The store is cleared at ON_DESTROY only when !isChangingConfigurations.
  5. Clearing cancels viewModelScope (SupervisorJob + Dispatchers.Main.immediate) before onCleared().
  6. Fragments’ stores live in FragmentManagerViewModel; activityViewModels() shares the Activity’s store.
  7. Navigation gives each back-stack entry its own store, cleared when the entry is popped.
  8. In Compose, viewModel() uses LocalViewModelStoreOwner; without Navigation that’s the whole Activity.
  9. Never hold an Activity, View or Fragment in a ViewModel.
  10. Process death ends it: keep what matters in SavedStateHandle or storage.

Then close the page and try the cross questions from memory.

The fine print

This is the current AndroidX source (October 2026). Details such as the default key and the Compose owner APIs evolve between lifecycle releases, so check the version you ship.

Next in this series: process death, and how SavedStateHandle, Bundle, Parcelable and Kotlin Serialization fit together.