Kahibaro
Discord Login Register

14.1 SharedPreferences

Understanding SharedPreferences

SharedPreferences is a simple key value storage mechanism that Android provides for saving small pieces of data. It is best suited for user settings, preferences, and any lightweight configuration that needs to persist when the app is closed and reopened.

When to Use SharedPreferences

SharedPreferences is designed for simple data that can be represented as pairs of a String key and a primitive value. Typical examples include whether a user has seen an onboarding screen, the last selected app theme, a username for convenience, or a flag that remembers if notifications are enabled.

It is not appropriate for large text files, images, complex objects, or big lists of data. Those use cases belong in files or databases. SharedPreferences fills a very specific role: small, structured, and quickly accessible values.

Use SharedPreferences only for small, simple key value data, such as settings and flags, not for large or complex datasets.

Getting a SharedPreferences Instance

In an Activity, you typically access a SharedPreferences file with the getSharedPreferences function. You provide a file name and a mode, and you receive a SharedPreferences object that gives you read access to the stored values.

A common pattern is to choose a constant file name that you reuse across your app, for example "app_prefs". As long as you use the same name and the same application context, you are working with the same preference file.

A basic example in an Activity looks like this:

val sharedPrefs = getSharedPreferences("app_prefs", MODE_PRIVATE)

MODE_PRIVATE tells Android that only your app can access this preferences file. Other modes exist historically, but for modern apps you use private mode only.

You can also use the application context if you are not in an Activity, for example:

val sharedPrefs = context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)

Both approaches refer to the same underlying file if the name and context refer to the same app.

Storing Data with an Editor

SharedPreferences itself is read only. To change values, you must obtain an editor object, place your changes in that editor, and then commit those changes. The editor collects modifications to multiple keys, and then writes them together when you are ready.

The workflow always follows the same pattern:

  1. Get the SharedPreferences instance.
  2. Call edit() to get a SharedPreferences.Editor.
  3. Use a put function such as putBoolean, putInt, putString, and so on.
  4. Save changes with apply() or commit().

For example, to store a simple boolean that tracks whether the user has seen a tutorial, you can write:

val sharedPrefs = getSharedPreferences("app_prefs", MODE_PRIVATE)
val editor = sharedPrefs.edit()
editor.putBoolean("has_seen_tutorial", true)
editor.apply()

There are different putX functions for different value types, such as:

putBoolean(key: String, value: Boolean)

putInt(key: String, value: Int)

putLong(key: String, value: Long)

putFloat(key: String, value: Float)

putString(key: String, value: String?)

There is also putStringSet for small sets of strings, which is useful when you want to store a collection of simple string values without moving to a database. Use it only for small sets.

You can remove a single key with remove, or completely clear all stored values in that preferences file with clear:

val editor = sharedPrefs.edit()
editor.remove("has_seen_tutorial")
editor.apply()
// Or to clear all keys
val clearEditor = sharedPrefs.edit()
clearEditor.clear()
clearEditor.apply()

apply vs commit

There are two ways to finalize your changes when you use a SharedPreferences editor, apply() and commit().

commit() writes the changes to storage synchronously and returns a Boolean that tells you whether the write succeeded. While it is writing, it blocks the current thread. If you call it on the main thread, you can briefly freeze your UI, especially if the device is slow.

apply() writes the changes to memory immediately, schedules the actual disk write to happen in the background, and returns immediately without giving you a success value. This is usually what you want for user preferences. The change is visible to your app as soon as apply() returns.

In most situations prefer apply() for SharedPreferences. Use commit() only when you must know immediately if the write succeeded and you are not blocking the UI thread.

Reading Values and Default Values

Reading from SharedPreferences is straightforward. You use a getX function that matches the type you stored. You must always pass a default value. This default is returned if the key does not exist in the preferences file.

Examples of reading different primitive types:

val sharedPrefs = getSharedPreferences("app_prefs", MODE_PRIVATE)
// Boolean
val hasSeenTutorial = sharedPrefs.getBoolean("has_seen_tutorial", false)
// Int
val launchCount = sharedPrefs.getInt("launch_count", 0)
// String
val username = sharedPrefs.getString("username", null)

If "has_seen_tutorial" was never stored, the code above will return false. If "launch_count" was never stored, it will return 0. For getString, the default is null which indicates an absent value in this example.

You can test for existence of a key explicitly if you need that information. The contains function lets you check whether a particular key is present:

val hasUsername = sharedPrefs.contains("username")

The return type of each getX function matches its corresponding putX function. You should avoid reading a different type than you wrote. For example, do not call getInt for a key that you stored with putString.

Typical Usage Patterns

One of the most common uses is to remember whether something should show only once. For example, an onboarding screen can be controlled by a single boolean flag in SharedPreferences. This pattern looks like:

val prefs = getSharedPreferences("app_prefs", MODE_PRIVATE)
val hasSeenOnboarding = prefs.getBoolean("has_seen_onboarding", false)
if (!hasSeenOnboarding) {
    // Show onboarding UI
    // After user finishes:
    prefs.edit()
        .putBoolean("has_seen_onboarding", true)
        .apply()
}

Another frequent pattern is tracking app launch count. You can read the current count, increment it, and write it back:

val prefs = getSharedPreferences("app_prefs", MODE_PRIVATE)
val currentCount = prefs.getInt("launch_count", 0)
val newCount = currentCount + 1
prefs.edit()
    .putInt("launch_count", newCount)
    .apply()

You can also store simple preferences such as an option to enable or disable notifications:

val prefs = getSharedPreferences("app_prefs", MODE_PRIVATE)
// Saving the choice
fun setNotificationsEnabled(enabled: Boolean) {
    prefs.edit()
        .putBoolean("notifications_enabled", enabled)
        .apply()
}
// Reading the choice
fun areNotificationsEnabled(): Boolean {
    return prefs.getBoolean("notifications_enabled", true)
}

In this case, the default is true, which means that if the user never changed the setting, notifications are considered enabled.

Keeping Keys Organized

As your app grows, you may end up with many keys scattered across the code, which becomes hard to maintain. A simple approach is to define all preference keys in one place as constants. This removes typos and makes refactoring easier.

An example in Kotlin might look like:

object PrefsKeys {
    const val FILE_NAME = "app_prefs"
    const val KEY_HAS_SEEN_ONBOARDING = "has_seen_onboarding"
    const val KEY_LAUNCH_COUNT = "launch_count"
    const val KEY_USERNAME = "username"
}

You then reference these constants instead of writing the same string literals in several places. This helps keep your SharedPreferences usage consistent throughout the app.

Threading and Safety Considerations

SharedPreferences is designed to be safe for use from multiple threads. Multiple parts of your app can read the same preferences instance without problems. For writes, if you use apply(), Android takes care of scheduling the file write in a background thread.

However, if your app tries to read and write the same key very rapidly from different places, be mindful that newer writes might override older values. It is still important to design your logic clearly so that you do not unintentionally overwrite data.

For very frequent and complex updates that must stay strictly consistent, SharedPreferences can become difficult to manage. In those situations, a proper database is usually a better solution.

Using SharedPreferences in Different Components

You can access SharedPreferences in Activities, Fragments, Services, or any other Android component as long as you have a proper context. In an Activity you usually call getSharedPreferences directly. In a Fragment you typically use requireContext().getSharedPreferences(...). In other classes you often receive a context in the constructor or through dependency injection and then call context.getSharedPreferences(...).

The important rule is to be consistent with the file name and the use of application context so that all components are reading and writing to the same logical preferences file when they are meant to share data.

SharedPreferences is a simple and efficient tool when you use it for the right kind of data. Once you understand how to get an instance, use an editor, choose between apply() and commit(), and organize your keys, you have everything you need to store persistent settings and flags in your app.

Views: 2

Comments

Please login to add a comment.

Don't have an account? Register now!