0Pricing
Android Academy · Lesson

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-preferences dependency
  • Create a single instance with preferencesDataStore delegate
  • Define type-safe keys with stringPreferencesKey, booleanPreferencesKey, etc.
  • Write with dataStore.edit { } inside a coroutine
  • Read as a Flow with dataStore.data.map { }
  • Handle IOException with .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.

All lessons in this course

  1. Kotlin Collections
  2. RecyclerView Basics
  3. RecyclerView Click Events
  4. SharedPreferences
  5. DataStore Preferences
  6. Adapters & DiffUtil
← Back to Android Academy