DataStore Preferences
Replace SharedPreferences with the modern DataStore library. Read data as a Kotlin Flow, write with edit{}, and handle errors safely.
DataStore Preferences is a free Android Academy lesson on CoddyKit — lesson 5 of 6. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Android Academy learning path, one of 6 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Why DataStore?
DataStore is the modern replacement for SharedPreferences. It solves real problems:
- SharedPreferences blocks the UI thread — DataStore is fully asynchronous
- SharedPreferences can throw uncaught exceptions — DataStore handles errors via Flow
- DataStore is type-safe (Preferences) or schema-safe (Proto)
- Works natively with Kotlin Coroutines and Flow
Two Types of DataStore
Choose the right type for your use case:
- Preferences DataStore — key-value pairs, no schema needed. Simplest to set up. Best for app settings, user preferences.
- Proto DataStore — typed objects using Protocol Buffers. Requires a .proto schema file. Best when you have structured data.
This lesson covers Preferences DataStore.
Adding the Dependency
Add DataStore to app/build.gradle:
// app/build.gradle
dependencies {
implementation 'androidx.datastore:datastore-preferences:1.1.1'
// Coroutines (likely already in your project)
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
}Creating a DataStore Instance
Create the DataStore once using the top-level delegate. Typically done in a standalone file:
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
// Top-level property — creates a single DataStore instance per Context
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "user_prefs")Defining Keys
Keys are type-safe objects, not plain strings. Create them with the right factory function:
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
object PreferencesKeys {
val DARK_MODE = booleanPreferencesKey("dark_mode")
val USER_NAME = stringPreferencesKey("user_name")
val LAUNCH_COUNT = intPreferencesKey("launch_count")
}Writing Data
Write inside a coroutine using edit { }:
import androidx.datastore.preferences.core.edit
// In a ViewModel or Repository:
suspend fun saveDarkMode(enabled: Boolean) {
context.dataStore.edit { prefs ->
prefs[PreferencesKeys.DARK_MODE] = enabled
}
}
suspend fun incrementLaunchCount() {
context.dataStore.edit { prefs ->
val current = prefs[PreferencesKeys.LAUNCH_COUNT] ?: 0
prefs[PreferencesKeys.LAUNCH_COUNT] = current + 1
}
}Reading Data with Flow
DataStore exposes data as a Flow. Use map to extract the value you need:
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
val darkModeFlow: Flow<Boolean> = context.dataStore.data
.map { prefs ->
prefs[PreferencesKeys.DARK_MODE] ?: false // default = false
}
val userNameFlow: Flow<String> = context.dataStore.data
.map { prefs ->
prefs[PreferencesKeys.USER_NAME] ?: "Guest"
}Collecting in ViewModel
Expose DataStore Flows from a ViewModel using stateIn for efficient collection:
class SettingsViewModel(private val repo: SettingsRepository) : ViewModel() {
val isDarkMode: StateFlow<Boolean> = repo.darkModeFlow
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = false
)
fun toggleDarkMode(enabled: Boolean) {
viewModelScope.launch {
repo.saveDarkMode(enabled)
}
}
}Observing in Activity/Fragment
Collect the StateFlow in the UI using lifecycleScope.launch:
// In Fragment:
lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.isDarkMode.collect { darkMode ->
binding.switchDarkMode.isChecked = darkMode
AppCompatDelegate.setDefaultNightMode(
if (darkMode) AppCompatDelegate.MODE_NIGHT_YES
else AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
)
}
}
}Error Handling
DataStore emits IOException if the file is corrupted. Catch it in your Flow pipeline:
import kotlinx.coroutines.flow.catch
import java.io.IOException
val safeFlow: Flow<Boolean> = context.dataStore.data
.catch { exception ->
if (exception is IOException) {
emit(emptyPreferences()) // return defaults
} else {
throw exception
}
}
.map { prefs ->
prefs[PreferencesKeys.DARK_MODE] ?: false
}DataStore vs SharedPreferences
Summary of why DataStore wins:
- DataStore reads/writes on a background thread automatically — no ANR risk
- SharedPreferences
apply()can silently fail; DataStore propagates errors via Flow - DataStore is reactive — UI updates automatically when data changes
- SharedPreferences has no migration path; DataStore has a built-in
SharedPreferencesMigration
Quick Check
What is the main mechanism DataStore uses to expose its data to the rest of the app?
Recap: DataStore Preferences
DataStore is the modern way to persist simple data:
- Add
datastore-preferencesdependency - Create a single instance with
preferencesDataStoredelegate - Define type-safe keys with
stringPreferencesKey,booleanPreferencesKey, etc. - Write with
dataStore.edit { }inside a coroutine - Read as a
FlowwithdataStore.data.map { } - Handle
IOExceptionwith.catch { emit(emptyPreferences()) }
Next: build efficient list adapters with DiffUtil.
Frequently asked questions
Is the “DataStore Preferences” lesson free?
Yes — the full text of “DataStore Preferences” is free to read here on the web, and the Android Academy course includes 6 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Android Academy course, upgrade to CoddyKit PRO.
What will I learn in “DataStore Preferences”?
Replace SharedPreferences with the modern DataStore library. Read data as a Kotlin Flow, write with edit{}, and handle errors safely. You practise Android Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Android Academy?
No prior experience is required. Android Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 5 of 6, so you can start here or from the beginning and move at your own pace.
How long does the “DataStore Preferences” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Android Academy lesson?
Yes. Every Android Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.