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. This is lesson 5 of 6. Any 3 lessons of this learning path are free to read in full — after that, CoddyKit PRO unlocks every lesson, plus hands-on practice with a built-in code editor and a 24/7 AI tutor. It is part of the Android Academy learning path, and your progress syncs across the web and the CoddyKit app. The Android Academy course includes 6 lessons in total.
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.
Learn Kotlin with an AI tutor — free
Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.
- Courses
- 36
- Lessons
- 152
Frequently Asked Questions
Is the “DataStore Preferences” lesson free?
Yes — any 3 lessons of the Android Academy learning path, including “DataStore Preferences”, are free to read in full here on the web. After that, CoddyKit PRO unlocks every lesson, plus interactive practice with a built-in code editor and a 24/7 AI tutor. The Android Academy course includes 6 lessons in total.
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, so you can start here or from the beginning and move at your own pace. This is lesson 5 of 6.
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.
All lessons in this course
- Kotlin Collections
- RecyclerView Basics
- RecyclerView Click Events
- SharedPreferences
- DataStore Preferences
- Adapters & DiffUtil