Skip to content

SharedPreferences — Complete Guide

DodaTech Updated 2026-06-24 2 min read

In this tutorial, you'll learn about SharedPreferences. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

The Problem

SharedPreferences writes block the UI thread, or apply() is called but the value isn't available immediately in another Process.

Wrong Approach ❌

// Calling commit on the main thread — blocks!
prefs.edit().putString("key", "value").commit()
// Creating multiple Editor instances
val editor1 = prefs.edit()
editor1.putString("key1", "value1")
editor1.apply()

val editor2 = prefs.edit() // New editor — key1 might not be written yet
editor2.putString("key2", "value2")
editor2.apply()

Output: UI jank from synchronous disk I/O. Race conditions between editors.

Right Approach ✅

class PreferencesManager(context: Context) {
    private val prefs = context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)

    // Use apply() for async writes
    fun saveString(key: String, value: String) {
        prefs.edit().putString(key, value).apply() // Async, non-blocking
    }

    // Use commit() only when you need a synchronous result
    fun saveStringSync(key: String, value: String): Boolean {
        return prefs.edit().putString(key, value).commit() // Returns success
    }

    fun getString(key: String, default: String = ""): String {
        return prefs.getString(key, default) ?: default
    }

    // Don't use SharedPreferences for large data
    fun saveUser(user: User) {
        // WRONG: serializing large objects to SharedPreferences
        val json = Gson().toJson(user)
        prefs.edit().putString("user_data", json).apply()
        // Use Room or DataStore instead
    }

    // Batch multiple edits
    fun saveMultiple(key1: String, v1: String, key2: String, v2: Int) {
        prefs.edit().run {
            putString(key1, v1)
            putInt(key2, v2)
            apply() // Single disk write for all changes
        }
    }

    // Remove
    fun remove(key: String) {
        prefs.edit().remove(key).apply()
    }

    // Clear all
    fun clear() {
        prefs.edit().clear().apply()
    }

    // Migration to DataStore
    // Prefer DataStore for new code
}

// Using this from Compose
@Composable
fun RememberPreference(prefsManager: PreferencesManager) {
    var name by remember { mutableStateOf(prefsManager.getString("name")) }
    // Note: doesn't react to changes from other sources
}

Output: Non-blocking SharedPreferences writes with proper editor usage.

Prevention

  • Use apply() for most writes (async), commit() only when you need the result.
  • Batch multiple edits in a single editor.
  • Use SharedPreferences only for small key-value data (< 100KB).
  • For reactive observation, migrate to DataStore or Room.
  • Never store complex objects (JSON) in SharedPreferences — use Room.

Common Mistakes with storage shared preferences

  1. Forgetting that lazy evaluation defers computation until the value is forced, causing space leaks with unevaluated thunks
  2. Using return to exit a function early instead of wrapping a pure value in the monad
  3. Mixing let bindings with <- bindings in do notation, producing type errors

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

### What is the difference between commit() and apply()?

commit() writes to disk synchronously and returns a boolean. apply() writes to the in-memory cache immediately and schedules disk write asynchronously. Use apply() for most cases.

### Is SharedPreferences thread-safe?

The single-Process instance is thread-safe. However, changes from one editor may not be visible to another editor until apply()/commit() is called.

### Why should I use DataStore instead?

DataStore uses Kotlin coroutines and Flow for reactive observation. It's asynchronous, consistent, and avoids the pitfalls of SharedPreferences (UI thread blocking, no reactive reads).

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro