SharedPreferences — Complete Guide
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
SharedPreferencesonly for small key-value data (< 100KB). - For reactive observation, migrate to
DataStoreorRoom. - Never store complex objects (JSON) in SharedPreferences — use Room.
Common Mistakes with storage shared preferences
- Forgetting that lazy evaluation defers computation until the value is forced, causing space leaks with unevaluated thunks
- Using
returnto exit a function early instead of wrapping a pure value in the monad - 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
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro