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 | 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:
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.