Managing UI State Across Configuration Changes with Android ViewModel
Learn how to use Android ViewModel to persist UI state across screen rotations and configuration changes while avoiding memory leaks and handling process death.
13 Jan 2026, 21:07 UTC

The Problem: State Loss During Rotation
In Android, configuration changes—such as rotating the screen, changing the system language, or switching to multi-window mode—trigger the destruction and recreation of the current Activity. If you store UI state (like a user's half-filled form or a loaded list of search results) in local Activity variables, that data is wiped. While onSaveInstanceState() can handle small amounts of data, it is inefficient for large datasets or asynchronous streams because it requires serializing data to a Bundle.
The ViewModel solves this by decoupling the UI data from the Activity's lifecycle. A ViewModel instance is retained in memory by the ViewModelStore until the Activity is finished permanently (e.g., the user presses the back button or finish() is called), allowing the new Activity instance to reconnect to the existing data immediately.
Implementing a Lifecycle-Aware ViewModel
To implement a ViewModel, you must extend the ViewModel class and instantiate it using ViewModelProvider. This ensures the Android framework manages the instance's longevity rather than the Activity creating a new object every time it restarts.
The following example demonstrates a ViewModel that manages a counter and a string, using StateFlow to provide reactive updates to the UI. This assumes Android Jetpack libraries (version 2.5.0+) are included in the project.
// 1. Define the ViewModel
class UserProfileViewModel : ViewModel() {
private val _userName = MutableStateFlow("Guest")
val userName: StateFlow<String> = _userName.asStateFlow()
private val _clickCount = MutableStateFlow(0)
val clickCount: StateFlow<Int> = _clickCount.asStateFlow()
fun updateName(newName: String) {
_userName.value = newName
}
fun incrementCount() {
_clickCount.value += 1
}
override fun onCleared() {
super.onCleared()
// Cancel long-running coroutines or close resources here
Log.d("ViewModel", "ViewModel cleared and resources released")
}
}
// 2. Use the ViewModel in the Activity
class ProfileActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_profile)
// Instantiate via ViewModelProvider to ensure persistence across rotation
val viewModel = ViewModelProvider(this).get(UserProfileViewModel::class.java)
// Observe StateFlow within the lifecycle scope
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.userName.collect { name ->
findViewById<TextView>(R.id.nameText).text = name
}
}
}
findViewById<Button>(R.id.updateBtn).setOnClickListener {
viewModel.incrementCount()
}
}
}Key Configuration Details
- ViewModelProvider(this): The
thisrefers to the Activity acting as theViewModelStoreOwner. This tells the provider which lifecycle the ViewModel should be tied to. - StateFlow: Used here instead of LiveData for better compatibility with Kotlin Coroutines and a guaranteed initial state.
- onCleared(): This is the only reliable place to clean up resources (like closing a database connection) because it is called only when the ViewModel is truly being destroyed, not during a rotation.
Critical Limitations and Memory Safety
While ViewModels persist through configuration changes, they are not a substitute for permanent storage. Understanding these boundaries prevents common crashes and memory leaks.
The Memory Leak Trap
Never store a reference to a View, Fragment, or Activity context inside a ViewModel. Because the ViewModel outlives the Activity, holding a reference to a View prevents the Garbage Collector from reclaiming the destroyed Activity's memory. If you need a Context, extend AndroidViewModel, which provides the Application context—a singleton that lasts for the entire app process.
Process Death vs. Configuration Change
A ViewModel survives a rotation, but it does not survive system-initiated process death (where Android kills the app in the background to reclaim RAM). To handle process death, you must use SavedStateHandle. This allows the ViewModel to read and write small amounts of data to the system's saved state bundle.
| Scenario | ViewModel State | SavedStateHandle | Room/DataStore |
|---|---|---|---|
| Screen Rotation | Preserved | Preserved | Preserved |
| System Process Death | Lost | Preserved | Preserved |
| User Closes App | Lost | Lost | Preserved |
Verification and Diagnostics
To verify your implementation is working correctly, follow these steps:
- Rotation Test: Enter data into your UI, rotate the device 90 degrees, and verify the data remains. If the data resets, you likely instantiated the ViewModel using a standard constructor (
val vm = UserProfileViewModel()) instead ofViewModelProvider. - Memory Profiling: Open the Android Studio Profiler. Rotate the device several times. If the number of
UserProfileViewModelinstances increases with every rotation, you have a leak or a provider misconfiguration. - Lifecycle Logging: Add a log statement to
onCleared(). Press the system back button. You should see the log trigger. Rotate the screen; the log should not trigger.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.