Android SharedPreferences Tutorial and Example

Saving state with onSaveInstanceState and ViewModel

Android can recreate an Activity when the device rotates, the window changes size, or the system reclaims an app that has been in the background. The replacement gets a new view hierarchy, so text held only in a field or adapter can vanish. State handling keeps the user's place without making the UI wait for a database or network request.

onSaveInstanceState and ViewModel solve related but different parts of this problem. The first stores a small snapshot in a Bundle for a later Activity instance; the second keeps an object in memory while configuration changes recreate the screen. A form often needs both: a ViewModel for active editing and saved state for recovery after process death.

Why Android activity state disappears

An Activity is not permanent storage. Its window, fields, and listeners belong to one instance, while the system may destroy that instance and create another during a configuration change. The saved Bundle is then offered to the new instance so it can rebuild the same visible state.

A ViewModel is retained through that recreation, but it is not automatically durable. If Android kills the entire process, all ordinary fields and ViewModels disappear. A draft in an EditText, selected ListView row, current tab, and scroll position therefore need deliberate treatment rather than an assumption that the object will survive.

For an Australian user opening a note while a train loses signal in a Sydney tunnel, this distinction is practical: the note should remain on screen when connectivity returns or the phone rotates, while the sync operation can retry separately. The screen should not treat a transient network gap as permission to erase local input.

Saving small values with onSaveInstanceState

Use onSaveInstanceState for values that describe the current screen and are small enough to serialise. Android calls the callback before many recreations and may retain the resulting state while the task is in the background. Store primitives, strings, primitive arrays, or small Parcelable values with stable keys.

private const val QUERY_KEY = "search_query"

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    outState.putString(QUERY_KEY, searchQuery)
    outState.putInt("selected_position", selectedPosition)
}

Restore the values in onCreate before listeners trigger work:

if (savedInstanceState != null) {
    searchQuery = savedInstanceState.getString(QUERY_KEY).orEmpty()
    selectedPosition = savedInstanceState.getInt("selected_position")
}

Do not put passwords, access tokens, large bitmaps, or complete records in the Bundle. It is a handoff for recreation, not a database, and excessive serialisation can cause latency or lost-state failures. Keep separate screens from reusing generic keys such as "position", and save semantic values rather than a formatted label.

Keeping larger state in a ViewModel

A ViewModel is a lifecycle-aware holder that survives configuration changes without putting every keystroke into a Bundle. It works well for an editable draft, a filter, a list of selected IDs, or a calculation that should remain while the Activity is rebuilt.

class DraftViewModel : ViewModel() {
    private val _draft = MutableStateFlow("")
    val draft: StateFlow<String> = _draft

    fun updateDraft(value: String) {
        _draft.value = value
    }
}

In the Activity, create the ViewModel once with by viewModels(), then collect its flow while the screen is started. A text watcher can send edits to updateDraft; the ViewModel remains the source of truth when the view is recreated.

private val draftViewModel: DraftViewModel by viewModels()

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        draftViewModel.draft.collect { value ->
            draftField.setText(value)
        }
    }
}

The retained ViewModel is cleared when its owner finishes for good, not merely when the phone rotates. When the basic pattern works, project-based coding practice can help you extend it into a notes or checklist project. The model is still not a substitute for Room or SQLite when the user expects a record to remain after leaving the screen.

Choosing the right state owner

Choose based on lifetime and size. A ViewModel suits working UI state that must follow the user through an Activity recreation. Saved instance state suits a compact snapshot that the system may hand back after the Activity has been destroyed.

A compact comparison makes the boundary clear:

Concern onSaveInstanceState ViewModel
Rotation Saves a small snapshot for restoration Remains in memory
Process death May return saved values if Android retained them Is removed with the process
Good fit Tab, query, IDs, scroll position Drafts, filters, transient calculations
Scope The Activity and its saved task state The owning Activity, Fragment, or other owner
Large data Avoid; serialisation has limits Avoid holding unbounded data in memory

When a ViewModel value must also survive process death, use SavedStateHandle with the appropriate ViewModel factory. It stores supported values through Android's saved-state mechanism while retaining the familiar ViewModel API:

class DraftViewModel(private val state: SavedStateHandle) : ViewModel() {
    val draft = state.getStateFlow("draft", "")

    fun updateDraft(value: String) {
        state["draft"] = value
    }
}

Use both mechanisms without duplicating ownership: keep the full editing state in the ViewModel, and save only the minimum needed to reconstruct it. If a value must be available days later or across logout boundaries, write it to Room/SQLite or another durable store rather than relying on either lifecycle mechanism.

Testing rotation and process death

Test recreation and process death separately. Enter a query, select a row, rotate the emulator, and confirm that the restored screen matches the previous view without firing duplicate requests. Then test a real device with a split-screen resize, since modern Android windows can recreate an Activity when their available size changes. For an Australian release distributed through Google Play, check representative Samsung and Google phones as well as the emulator.

For the stronger case, send the app to the background and use Android Studio's process inspection or the emulator controls to terminate it, then reopen the task from recents. A plain ViewModel will be gone; a Bundle or SavedStateHandle may restore the small values it saved. The exact restore point depends on the system state, so the screen still needs sensible defaults.

Australian formats provide useful test cases. Store a date as an ISO value and render it day-first, rather than saving 04/07 and assuming it means the same thing everywhere. Keep a user preference such as “favourite” in the model, and calculate AEDT or AEST display times from the restored instant. These details are small, but they prevent an apparently correct restoration from changing meaning.

A dependable state-saving checklist

A reliable screen has one clear owner for each piece of state and a deliberate policy for every lifecycle event. Decide whether a value is merely visible UI state, work that should survive rotation, or durable user data before writing the save code.

The following checks keep the implementation predictable as the app grows by establishing clear ownership and recovery boundaries for UI state. Developers should keep editable text, filters, selected IDs, and transient calculations in a ViewModel. They must save only small, screen-specific values in onSaveInstanceState, using stable namespaced keys. Furthermore, they need to restore state before listeners start, and make collection and click handlers safe to repeat.

Additional checks require using SavedStateHandle when a ViewModel value must survive process death. Teams must store completed notes, orders, and accounts in Room or SQLite; reserve SharedPreferences for small settings. Finally, they should test rotation, resizing, background restoration, and process death on an emulator and a real Android device.