How to Fix Android ViewModel Not Persisting State
In this tutorial, you'll learn about How to Fix Android ViewModel Not Persisting State. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
The Problem
Your ViewModel loses data when the app is backgrounded and killed:
class MyViewModel : ViewModel() {
var count = 0 // reset after process death
}
After process recreation, count is back to 0. The ViewModel survives configuration changes but not process death.
Quick Fix
Step 1: Use SavedStateHandle in the ViewModel
class MyViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
val count = savedStateHandle.getLiveData<Int>("count")
fun increment() {
count.value = (count.value ?: 0) + 1
}
}
Add a Factory or use Hilt/AndroidX assisted injection:
class MyViewModelFactory(
private val owner: SavedStateRegistryOwner,
private val defaultArgs: Bundle? = null
) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
override fun <T : ViewModel> create(key: String, modelClass: Class<T>, handle: SavedStateHandle): T {
return MyViewModel(handle) as T
}
}
Step 2: Register the ViewModel correctly
// Scoped to Navigation back stack entry
val viewModel: MyViewModel by viewModels()
// Scoped to Activity (shared across fragments)
val viewModel: MyViewModel by activityViewModels()
Using by viewModels() inside a NavBackStackEntry scopes the ViewModel to that destination. If you navigate away and back, a new ViewModel is created.
Step 3: Do not store large data in ViewModel directly
// Wrong - bitmap survives rotation but not process death
class MyViewModel : ViewModel() {
var bitmap: Bitmap? = null
}
// Right - persist to disk or SavedStateHandle for critical state
class MyViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
val imageUri = savedStateHandle.get<String>("image_uri")
}
Step 4: Enable ViewModel state with Jetpack Navigation
<argument
android:name="itemId"
android:defaultValue="0"
app:argType="integer" />
Navigation stores arguments in SavedStateHandle automatically:
class DetailViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
val itemId = savedStateHandle.get<Int>("itemId") ?: 0
}
Step 5: Check ViewModel scope
| Scope | Lifecycle |
|---|---|
| Activity | Until Activity finishes |
| Fragment | Until Fragment detaches |
| NavBackStackEntry | Until entry is popped |
ViewModel is never garbage collected during its scope. It is cleared when the scope's LifecycleOwner is permanently destroyed.
Prevention
- Use
SavedStateHandlefor any state that must survive process death. - Avoid storing complex objects directly in ViewModel fields.
- Use
StateFloworLiveDataexposed from the ViewModel.
Common Mistakes with viewmodel error
- Overlapping type class instances that cause GHC to reject the program with ambiguous dispatch errors
- Non-exhaustive pattern matches that compile with warnings then crash at runtime
- Misunderstanding that
Stringis[Char]with poor performance for large text operations
These mistakes appear frequently in real-world Android code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.
Practice Exercise
Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.
This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.
FAQ
DodaTech Tool Reference
Doda Browser's Memory Profiler can track ViewModel instances and detect leaks from incorrectly scoped ViewModel references.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro