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.
// 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
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:
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:
suspend fun setDarkMode(enabled: Boolean) {
context.dataStore.edit { prefs -> // edit suspends; runs off the main thread
prefs[SettingsKeys.DARK_MODE] = enabled
}
}Tip
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.