100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Kotlin

DataStore for Preferences

Jetpack DataStore is the modern, coroutine-based replacement for SharedPreferences, offering asynchronous, transactionally safe, Flow-driven key-value and typed storage.

Data & NetworkingIntermediate8 min readJul 8, 2026
Analogies

DataStore for Preferences

DataStore is Jetpack's replacement for SharedPreferences, designed to fix the older API's biggest problems: SharedPreferences.apply() writes to disk on a background thread but offers no signal for completion or failure, its getters run synchronously and can block the calling thread on first access, and it has no built-in mechanism for atomic, transactional updates across multiple keys. DataStore comes in two flavors — Preferences DataStore, which stores untyped key-value pairs similar to SharedPreferences but through a fully asynchronous Flow-based API, and Proto DataStore, which stores strongly typed objects defined via Protocol Buffers, gaining compile-time schema safety at the cost of extra setup. Both variants persist data through Kotlin coroutines and expose the current value as a Flow, so reads are inherently asynchronous and observable, and writes go through a suspend function that only returns once the change is durably applied.

🏏

Cricket analogy: SharedPreferences.apply() writing silently is like a scorer jotting a run down without confirming the umpire signaled it, while DataStore's Flow-based writes are like waiting for the third umpire's confirmed decision before updating the board.

Reading and writing with Preferences DataStore

A Preferences DataStore instance is typically created once per app process via the property delegate preferencesDataStore(name = ...) on Context. Keys are declared with typed helpers such as stringPreferencesKey, intPreferencesKey, or booleanPreferencesKey, which prevents accidentally reading a String value as an Int. Reading a value means collecting dataStore.data, a Flow<Preferences>, and mapping out the specific key you care about; because it's a Flow, any UI observing it recomposes automatically whenever the value changes elsewhere in the app. Writing uses dataStore.edit { prefs -> ... }, a suspend function that applies the given transform block atomically — either the whole block succeeds and is persisted, or none of it is, which eliminates the partial-write races that plagued raw SharedPreferences.Editor usage under concurrent access.

🏏

Cricket analogy: stringPreferencesKey vs intPreferencesKey is like a scorebook enforcing that 'batsman name' columns can't accidentally hold a run count, preventing a scorer from writing '45' into the wrong column.

kotlin
private val Context.userPrefsDataStore by preferencesDataStore(name = "user_prefs")

object PrefsKeys {
    val DARK_MODE = booleanPreferencesKey("dark_mode")
    val USERNAME = stringPreferencesKey("username")
}

class UserPreferencesRepository(private val context: Context) {

    val isDarkMode: Flow<Boolean> = context.userPrefsDataStore.data
        .map { prefs -> prefs[PrefsKeys.DARK_MODE] ?: false }

    suspend fun setDarkMode(enabled: Boolean) {
        context.userPrefsDataStore.edit { prefs ->
            prefs[PrefsKeys.DARK_MODE] = enabled
        }
    }

    suspend fun setUsername(name: String) {
        context.userPrefsDataStore.edit { prefs ->
            prefs[PrefsKeys.USERNAME] = name
        }
    }
}

Preferences DataStore vs. Proto DataStore

Preferences DataStore is the closer drop-in replacement for SharedPreferences and is the right default for simple flags, small settings, and toggles where a loose key-value model is acceptable. Proto DataStore requires defining a .proto schema and generating a typed data class from it, which adds build tooling overhead but gives you compile-time guarantees that every read and write matches a fixed schema — valuable when the stored data is structurally complex, like a whole settings object with nested fields, where a typo'd string key in Preferences DataStore would otherwise be a silent runtime bug.

🏏

Cricket analogy: Preferences DataStore for simple toggles is like a scorer using a loose tally sheet for quick notes, while Proto DataStore's generated schema is like an official ICC scorecard with fixed, validated fields for a full Test match record.

Think of Preferences DataStore as SharedPreferences with coroutines and Flow bolted on properly, and Proto DataStore as a fully typed, schema-validated settings object — the same relationship Room has to raw SQLite.

DataStore does not support partial updates to large, complex objects efficiently the way a database does — every read of dataStore.data emits the entire Preferences object. For data with relational structure or that needs querying, prefer Room; reserve DataStore for small, flat, app-wide settings.

  • DataStore replaces SharedPreferences with a fully asynchronous, coroutine- and Flow-based API.
  • Preferences DataStore stores untyped key-value pairs; Proto DataStore stores strongly typed, schema-defined objects.
  • dataStore.data exposes a Flow<Preferences> so reads are observable and drive automatic recomposition.
  • dataStore.edit { } is a suspend function that applies updates atomically and transactionally.
  • Typed key helpers like stringPreferencesKey prevent type-mismatch bugs that plain string keys allowed in SharedPreferences.
  • DataStore is best for small, flat settings; structured or queryable data belongs in Room.

Practice what you learned

Was this page helpful?

Topics covered

#Kotlin#AndroidWithJetpackComposeStudyNotes#MobileDevelopment#DataStoreForPreferences#DataStore#Preferences#Reading#Writing#StudyNotes#SkillVeris