Surviving Process Death with Android ViewModel and SavedStateHandle
When Android kills an application in the backgronud, the system may later restore it exactly where the user left off. To keep UI state across this recreation you can combine ViewModel with SavedStateHandle. The handle acts like a tiny key-value bundle that survives both configuration changes and process death.
Step-by-step implementation
1. Create a ViewModel that receives SavedStateHandle
public class CounterViewModel extends ViewModel {
private static final String KEY_COUNT = "count";
private final SavedStateHandle state;
private final MutableLiveData<Integer> counter;
public CounterViewModel(SavedStateHandle savedStateHandle) {
this.state = savedStateHandle;
// If the key exists we already have a value, otherwise default to 0
if (!state.contains(KEY_COUNT)) {
state.set(KEY_COUNT, 0);
}
counter = state.getLiveData(KEY_COUNT);
}
public LiveData<Integer> getCounter() {
return counter;
}
public void increment() {
Integer current = counter.getValue();
if (current != null) {
state.set(KEY_COUNT, current + 1);
}
}
}
2. Wire the ViewModel in the Activity
public class CounterActivity extends AppCompatActivity {
private ActivityCounterBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityCounterBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
CounterViewModel vm = new ViewModelProvider(
this,
new SavedStateViewModelFactory(getApplication(), this)
).get(CounterViewModel.class);
binding.setViewModel(vm);
binding.setLifecycleOwner(this);
}
}
3. Layout snippet (activity_counter.xml)
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.example.CounterViewModel" />
</data>
<LinearLayout
android:orientation="vertical"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="@{String.valueOf(viewModel.counter)}"
android:textSize="48sp"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:text="Add"
android:onClick="@{() -> viewModel.increment()}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</LinearLayout>
</layout>
Comparison with other persistance options
- File storage: Good for large binary data such as images. Combine memory cache with disk cache to reduce network round-trips.
- Remote storage: Keep user profiles or large data sets on a server so they are accessible from any device.
- SharedPreferences: Lightweight key-value pairs, ideal for flags or simple settings like "last logged-in username".
- SQLite + ContentProvider: Structured local database for complex queries. ContentProvider wraps the data source and exposes it to other apps via a uniform URI interface.