Here's the question that trips up almost every Android developer at some point: why does my data disappear when I rotate the screen?
The answer is that when you rotate your phone, Android destroys and recreates the entire Activity. Any data stored in that Activity — gone. Variables reset, lists cleared, API calls fired again. It's not a bug. It's by design. And the ViewModel exists specifically to solve this.
But the ViewModel's lifecycle is a bit nuanced. It survives screen rotation. It does NOT survive the user pressing back or the system killing your app. Knowing exactly when it lives, when it dies, and what to do in each case is the difference between an app that feels solid and one that randomly loses state. Let's break it down.
The ViewModel Lifecycle in Plain English
Look at the diagram above. The key insight is the gap between when the Activity is destroyed during rotation and when the ViewModel is cleared. That gap is everything.
Here's what actually happens step by step:
- Activity starts → ViewModel is created and associated with it
- Screen rotates → Activity is destroyed and recreated. ViewModel stays alive in memory
- New Activity instance → gets the same ViewModel instance. All data intact
- User presses back / Activity.finish() called → ViewModel is cleared.
onCleared()fires - System kills the process → Everything is gone including ViewModel. Use SavedStateHandle for this case
| Event | Activity | ViewModel |
|---|---|---|
| Screen rotation | ❌ Destroyed + recreated | ✅ Survives |
| Language/theme change | ❌ Destroyed + recreated | ✅ Survives |
| App goes to background | ⚠️ Paused/stopped | ✅ Survives |
| User presses back | ❌ Finished | ❌ Cleared |
finish() called |
❌ Finished | ❌ Cleared |
| System kills process (low memory) | ❌ Destroyed | ❌ Gone — use SavedStateHandle |
ViewModel Creation — It's a Singleton Per Scope
A ViewModel is created once and reused for the lifetime of its scope. The first time you request it, it's instantiated. Every subsequent request returns the same instance — even after rotation.
// Activity — ViewModel scoped to this Activity
class MainActivity : AppCompatActivity() {
private val viewModel: NewsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// First call: creates NewsViewModel
// After rotation: returns SAME instance
viewModel.fetchNews()
}
}
onCreate() fires again and you'd trigger another network request unnecessarily. Guard it with a flag or load data in init {} inside the ViewModel instead.
Fragment ViewModel Scoping — Two Flavours
In Fragments, you have two choices depending on whether you want the ViewModel shared with the host Activity or private to the Fragment:
class MyFragment : Fragment() {
// Fragment-scoped — unique to this Fragment
// Cleared when Fragment is destroyed
private val fragmentViewModel: MyViewModel by viewModels()
// Activity-scoped — shared with Activity and other Fragments
// Cleared only when Activity is finished
private val sharedViewModel: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Use fragmentViewModel for data only this Fragment needs
fragmentViewModel.loadLocalData()
// Use sharedViewModel for data shared between Fragments
// e.g. selected item in master-detail layout
sharedViewModel.selectedItem.observe(viewLifecycleOwner) { item ->
binding.tvDetail.text = item.name
}
}
}
A Real ViewModel That Uses Its Lifecycle Correctly
Here's a news app ViewModel that handles loading, error state, and proper cleanup — the way you'd actually write it in production:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class NewsViewModel(private val repository: NewsRepository) : ViewModel() {
private val _uiState = MutableStateFlow<NewsUiState>(NewsUiState.Loading)
val uiState: StateFlow<NewsUiState> = _uiState
// Load data once on creation — not every time the UI resubscribes
init {
fetchNews()
}
fun fetchNews() {
viewModelScope.launch {
_uiState.value = NewsUiState.Loading
try {
val articles = repository.fetchLatestNews()
_uiState.value = NewsUiState.Success(articles)
} catch (e: Exception) {
_uiState.value = NewsUiState.Error(e.message ?: "Unknown error")
}
}
}
// onCleared() fires when the ViewModel is permanently destroyed
// Use it to cancel subscriptions, close streams, or log analytics
override fun onCleared() {
super.onCleared()
// viewModelScope coroutines are cancelled automatically
// but if you have custom resources, clean them up here
repository.cancelOngoingRequests()
}
}
sealed class NewsUiState {
object Loading : NewsUiState()
data class Success(val articles: List<NewsArticle>) : NewsUiState()
data class Error(val message: String) : NewsUiState()
}
Observing in the Fragment with repeatOnLifecycle
class NewsFragment : Fragment() {
private val viewModel: NewsViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
// repeatOnLifecycle — pauses collection when UI is in background
// Resumes when UI comes back to STARTED state
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
when (state) {
is NewsUiState.Loading -> showLoading()
is NewsUiState.Success -> showArticles(state.articles)
is NewsUiState.Error -> showError(state.message)
}
}
}
}
}
}
onCleared() — What to Actually Do There
Most tutorials show an empty onCleared() and move on. Here's what you might actually need to do there:
override fun onCleared() {
super.onCleared()
// 1. Cancel custom coroutine jobs (viewModelScope jobs cancel automatically)
customJob?.cancel()
// 2. Close database cursors or streams
databaseCursor?.close()
// 3. Unregister listeners that were registered outside viewModelScope
locationManager.removeUpdates(locationListener)
// 4. Log analytics — useful to know when a screen session ends
analytics.logScreenEnd(screenName, sessionDuration)
}
viewModelScope.launch { } are cancelled automatically when onCleared() fires. You don't need to cancel them manually — just handle any non-coroutine resources.
The One Thing ViewModel Does NOT Survive — Process Death
This is the caveat nobody mentions clearly enough. If the system kills your app process due to low memory — which happens when the app is in the background — the ViewModel is gone. When the user comes back to the app, everything starts from scratch.
For data that must survive process death, use SavedStateHandle:
class UserViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// This value survives BOTH rotation AND process death
val searchQuery: StateFlow<String> =
savedStateHandle.getStateFlow("searchQuery", "")
fun updateSearchQuery(query: String) {
savedStateHandle["searchQuery"] = query
}
}
For a deep dive into when to use SavedStateHandle vs LiveData vs StateFlow, see our ViewModel State Management guide.
Best Practices
- Load data in ViewModel's init block — not in
onCreate(). After rotation,onCreate()fires again butinitdoesn't. This prevents unnecessary duplicate API calls. - Never hold references to Activity or Fragment in ViewModel — ViewModel outlives Activity. Holding a reference causes a memory leak. If you need Context, use
AndroidViewModelfor Application context only. - Use repeatOnLifecycle when collecting flows — wrapping flow collection in
repeatOnLifecycle(STARTED)ensures collection pauses when the app goes to background, saving battery and preventing wasted updates. - Use by viewModels() not ViewModelProvider directly — it's the idiomatic Kotlin way, requires less boilerplate, and the KTX extension handles the factory automatically.
- Use SavedStateHandle for critical user input — search queries, form drafts, and navigation arguments should survive process death. ViewModel alone isn't enough for this.
Frequently Asked Questions
Does ViewModel survive screen rotation?
Yes — ViewModel survives all configuration changes including screen rotation, language changes, and theme changes. The Activity is destroyed and recreated but the same ViewModel instance is returned. This is the primary purpose of ViewModel.
When is ViewModel cleared and onCleared() called?
ViewModel is cleared when the associated Activity is permanently finished — when the user presses back or finish() is called. It is NOT cleared during configuration changes. onCleared() is your signal to release any non-coroutine resources.
Does ViewModel survive process death?
No — ViewModel does not survive process death. If the system kills your app due to low memory while it's in the background, the ViewModel is gone. Use SavedStateHandle to persist data that must survive process death.
What is the difference between by viewModels() and by activityViewModels()?
by viewModels() creates a ViewModel scoped to the Fragment — destroyed when the Fragment is destroyed. by activityViewModels() returns a ViewModel scoped to the host Activity — shared between all Fragments and destroyed only when the Activity finishes. Use activityViewModels() for sharing data between Fragments.
- ViewModel survives rotation and all configuration changes
- ViewModel is cleared when Activity finishes — back press or
finish() - ViewModel does NOT survive process death — use SavedStateHandle for that
- Load data in
init {}notonCreate()to avoid duplicate API calls - Use
by activityViewModels()to share data between Fragments - Use
repeatOnLifecycle(STARTED)when collecting flows in Fragments viewModelScopecoroutines cancel automatically — handle other resources inonCleared()