Android SharedPreferences Tutorial and Example

Using SharedPreferences to Store User Preferences in Android

Android applications often need to remember small choices between sessions. A user may select dark mode, enable notifications, choose a preferred language, or dismiss an onboarding screen. These values are simple, but losing them whenever the app closes creates a poor experience.

SharedPreferences provides a straightforward way to save primitive data in key-value pairs. It is suitable for settings that do not require complex relationships, such as Boolean flags, text values, and numbers. The data remains available after the application restarts.

This approach is useful in beginner Android projects because it requires little code and works well with activities, fragments, dialogs, and ListViews. For example, a travel app could remember whether a user prefers temperatures in Celsius or has selected Australian dollars for prices.

Modern Android projects should also be aware of Jetpack DataStore. SharedPreferences remains common in existing applications and is perfectly adequate for small settings, while DataStore is generally preferred for new production code requiring stronger asynchronous and type-safe behaviour.

Understanding Preference Storage

SharedPreferences stores values in an XML file owned by the application. Developers interact with this file through a key, such as dark_mode, and a matching value. The API supports strings, integers, floats, Booleans, and sets of strings.

The stored data belongs to the app’s private storage, so another ordinary application cannot read it. It is automatically retained when the user closes or restarts the app. Uninstalling the app normally removes the preferences as well, which means they should not be used for information that must survive a reinstall.

A common use case is a settings screen for an Australian audience. The app might remember a preference for Australian dollars, a 24-hour clock, or Celsius temperatures. It could also store whether a user has selected public transport information for Sydney’s Opal network or Melbourne’s myki services.

Creating and Updating Preferences

Kotlin provides a concise way to access a preference file. The following example creates a private file named app_settings and reads a Boolean value. The default value is false when the key has not been saved.

val preferences = getSharedPreferences(
    "app_settings",
    MODE_PRIVATE
)

val darkModeEnabled = preferences.getBoolean("dark_mode", false)

To save a value, call edit(), add the required key-value pair, and finish with apply() or commit().

preferences.edit()
    .putBoolean("dark_mode", true)
    .putString("currency", "AUD")
    .putInt("text_size", 16)
    .apply()

apply() updates the in-memory object immediately and writes the change asynchronously. It is normally the best choice for user settings because it avoids blocking the main thread. commit() writes synchronously and returns a success value, but it can slow the interface if used during normal screen interaction.

Use stable, descriptive keys and keep them in one place where possible. This prevents spelling mistakes between the code that writes a preference and the code that reads it.

object PreferenceKeys {
    const val DARK_MODE = "dark_mode"
    const val CURRENCY = "currency"
    const val HAS_SEEN_WELCOME = "has_seen_welcome"
}

Selecting the Right Storage Option

SharedPreferences is convenient, but it is not the ideal solution for every kind of information. A shopping cart with many products, for example, is better represented by a database. A preference file should remain small and focused on configuration.

Requirement Suitable choice Reason
Dark mode or notification switch SharedPreferences Simple primitive values
New asynchronous settings storage Jetpack DataStore Safer typed access and coroutine support
List of saved recipes Room database Structured records and queries
Login token or sensitive credential Encrypted storage Additional protection is required
Temporary screen state ViewModel or saved state Appropriate for the current UI lifecycle

DataStore is a strong alternative for new applications, especially when settings are observed as a Kotlin Flow. However, SharedPreferences is still useful when maintaining an existing codebase or teaching fundamental Android storage concepts.

Never treat SharedPreferences as secure storage. A rooted device, backup process, debugging environment, or compromised application may expose its contents. Avoid storing passwords, payment details, or unencrypted authentication secrets. For sensitive values, investigate Android Keystore-backed solutions and encrypted storage libraries.

Connecting Preferences to the User Interface

A preference becomes useful when it is connected to a visible control. A Switch can control dark mode, while a Spinner can allow users to select a currency or measurement system. The current value should be loaded when the screen opens, then saved when the user changes it.

val darkModeSwitch = findViewById<Switch>(R.id.darkModeSwitch)

darkModeSwitch.isChecked =
    preferences.getBoolean(PreferenceKeys.DARK_MODE, false)

darkModeSwitch.setOnCheckedChangeListener { _, enabled ->
    preferences.edit()
        .putBoolean(PreferenceKeys.DARK_MODE, enabled)
        .apply()
}

For a selection control, save a stable value rather than the displayed label. A currency selector could store AUD, NZD, or USD, while the interface displays “Australian dollar” or “New Zealand dollar”. This makes the saved data easier to interpret if labels change later.

Australian applications should also avoid assuming that every user wants the same regional format. Dates such as 24/03/2025, Celsius temperatures, Australian dollars, and local spelling may be sensible defaults, but a clear setting gives users control. Apps distributed through Google Play in Australia should test both small and large screens, including common devices used in Sydney, Brisbane, Perth, and regional areas.

Checking and Maintaining Saved Values

Preferences should be tested across the complete application lifecycle. Change a setting, close the activity, stop the application, and reopen it to confirm that the selected value remains. Also test the first launch, where no key exists and the default must be sensible.

Keep preference names stable after release. Renaming dark_mode to darkTheme without migration makes existing users lose their setting. If a value’s meaning changes, read the old key, convert it, and save the new representation. Removing unused keys can be handled with remove().

A small checklist helps prevent common implementation problems:

  • Use constants for preference keys.
  • Supply a clear default for every read operation.
  • Prefer apply() for ordinary settings updates.
  • Keep private preferences separate from sensitive credentials.

When testing a settings screen, verify the following behaviours:

  • The control reflects the saved value when the screen opens.
  • A changed value remains after an app restart.
  • Defaults work after clearing app data.
  • Invalid or obsolete values do not crash the application.

For example, clearing app data from Android settings removes the preference file and returns the app to its initial state. This is valuable when testing first-run onboarding or checking whether a new default is applied correctly.

SharedPreferences is a practical tool for small, persistent choices. With consistent keys, safe defaults, appropriate privacy decisions, and careful lifecycle testing, it can reliably support settings such as themes, units, notification choices, and regional display options.