DataStore

Preferences DataStore Versus SharedPreferences

SharedPreferences, Android's key-value store since API 1, hides blocking disk I/O: the first read loads the whole XML file on the calling thread, and the system waits for apply()'s queued writes when an activity pauses, a known source of jank and ANRs. DataStore 234 replaces it with coroutines:

SharedPreferences and Preferences DataStore
SharedPreferences Preferences DataStore
Reading Synchronous, may block the main thread Flow, off the main thread
Writing apply() or blocking commit() suspend edit { }, one transaction
Errors Swallowed IOException in the Flow

Keys are typed, and a delegate creates one store per file for the whole process:

data/Settings.kt: BookNest's settings in Preferences DataStore (excerpt)Kotlin
private val Context.settingsStore: DataStore<Preferences> by preferencesDataStore("settings")
@Singleton
class SettingsRepository @Inject constructor(@ApplicationContext context: Context) {
  private val store = context.settingsStore               // one instance per file, per process
  private val theme = stringPreferencesKey("theme")
  private val dynamic = booleanPreferencesKey("dynamic_color")
  val settings: Flow<Settings> = store.data
    .catch { if (it is IOException) emit(emptyPreferences()) else throw it }  // bad file
    .map { p ->
      Settings(p[theme]?.let(ThemeMode::valueOf) ?: ThemeMode.SYSTEM, p[dynamic] ?: false)
    }
    ...
  suspend fun setTheme(mode: ThemeMode) { store.edit { it[theme] = mode.name } }

Never create two DataStore objects for one file in a process: DataStore throws IllegalStateException. To move from SharedPreferences, pass produceMigrations = { listOf(SharedPreferencesMigration(it, "old")) } to the delegate; it copies the old values on first read.