Phase 4 · Data & PersistenceModule 21~26 min read

Local Storage with DataStore

Persist simple settings and preferences asynchronously with Jetpack DataStore.

What you'll learn

Not all data needs a database. A theme choice, a username, whether the user has seen onboarding — these are small key–value settings, and Jetpack DataStore is the modern way to store them. It's asynchronous, safe, and Flow-based, so it fits everything you just learned.

By the end you'll be able to:

  • Explain what DataStore is and when to use it
  • Read preferences as a Flow
  • Write preferences with edit
  • Understand why it replaces SharedPreferences

What DataStore is

Preferences DataStore stores simple key–value pairs (booleans, strings, numbers) asynchronously. Reads come back as a Flow that re-emits whenever the value changes, and writes are suspend functions that run off the main thread. It's ideal for small, unstructured settings — for structured, queryable data (a list of tasks), use Room in the next module.

DataStore.kt
// build.gradle:  implementation("androidx.datastore:datastore-preferences:1.x")

// One DataStore for the whole app, created at file scope:
val Context.dataStore by preferencesDataStore(name = "settings")

// Define typed keys:
object SettingsKeys {
    val DARK_MODE = booleanPreferencesKey("dark_mode")
    val USERNAME = stringPreferencesKey("username")
}

Note

Create the DataStore once at file scope with the preferencesDataStore delegate — never create multiple instances for the same file, or they'll fight over it. In a real app you'd inject it (Phase 6), but the delegate is fine to start.

Reading preferences

Access the flow via dataStore.data and map it to the key you want, supplying a default for the first run when nothing is stored yet:

SettingsRepository.kt
class SettingsRepository(private val context: Context) {

    // Reading is a Flow — it emits again whenever the value changes.
    val darkMode: Flow<Boolean> = context.dataStore.data
        .map { prefs -> prefs[SettingsKeys.DARK_MODE] ?: false }   // default false
}

Because it's a Flow, your UI stays in sync automatically: expose it through the ViewModel and collect it with collectAsStateWithLifecycle(), and the moment the setting changes anywhere, every screen reading it updates.

Writing preferences

Writing uses edit, a suspend function you call from a coroutine. Inside the block you set keys on a mutable snapshot; DataStore persists it atomically:

SettingsRepository.kt
suspend fun setDarkMode(enabled: Boolean) {
    context.dataStore.edit { prefs ->        // edit suspends; runs off the main thread
        prefs[SettingsKeys.DARK_MODE] = enabled
    }
}

Tip

Notice how naturally this closes the loop with theming: store DARK_MODE in DataStore, expose it as state, and feed it into your AppTheme's darkTheme parameter. The user's choice now persists across launches — a real, shippable feature built from pieces you already know.

From SharedPreferences

The old API, SharedPreferences, is synchronous — its reads can block the main thread — and it has no built-in error handling or reactive updates. DataStore fixes all three: it's coroutine-based (never blocks the UI thread), reports errors through the flow, and pushes updates reactively. For new apps, always prefer DataStore. DataStore can even migrate existing SharedPreferences data for you via SharedPreferencesMigration.

Recap & quick check

Key takeaways

  • DataStore stores small key–value settings asynchronously — themes, flags, a username.
  • Reads are a Flow that re-emits on change; provide a default for the first run.
  • Writes use the suspend edit { } block and run off the main thread.
  • Create the DataStore once with the preferencesDataStore delegate.
  • Prefer DataStore over SharedPreferences: async, reactive, and safe; use Room for structured data.

Quick check

1. What is DataStore best suited for?

2. How do you read a value from DataStore?

3. How do you write a preference?

4. Why prefer DataStore over SharedPreferences?

5. How should you create the DataStore instance?

For settings, DataStore is perfect. For real structured data — lists you query, sort, and relate — you need a database. Enter Room.