Работа с настройками — неотъемлемая часть любого приложения. И если SharedPreferences знакома многим, то Jetpack DataStore предлагает более современный, асинхронный и потокобезопасный способ хранения пар «ключ‑значение». В этом уроке мы разберём, как подключить библиотеку в Android Studio, сохранить пользовательские параметры и прочитать их обратно, используя все возможности языка Kotlin.
Почему DataStore лучше SharedPreferences
SharedPreferences работает синхронно, и при вызове commit() или apply() на главном потоке интерфейс может притормаживать. DataStore устраняет этот недостаток: все операции выполняются асинхронно с помощью корутин, а наружу отдаётся Flow — реактивный поток данных. Больше не нужно гадать, сохранилось ли значение, или вручную обновлять интерфейс. Как только настройка изменяется, все подписчики получают свежее состояние.
Два типа DataStore: Preferences и Proto
Библиотека предоставляет две реализации. Первая — Preferences DataStore — не требует описания схемы и подходит для простых значений: строк, чисел, булевых флагов. Вторая — Proto DataStore — основана на Protocol Buffers, гарантирует типобезопасность и подходит для сложных объектов. Сегодня мы изучим первый вариант как наиболее доступный для начала.
Подключение DataStore к проекту
Откройте build.gradle.kts уровня модуля и добавьте зависимость. На момент написания статьи актуальна версия 1.1.3, которая совместима с Kotlin 2.x и последними версиями Android Studio.
dependencies {
implementation("androidx.datastore:datastore-preferences:1.1.3")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0")
}
Создание хранилища и ключей
Входная точка — экземпляр DataStore<Preferences>, который создаётся один раз через делегат preferencesDataStore. Ключи объявляются с помощью функции preferencesKey(), где нужно указать имя ключа и ожидаемый тип. Ключи принято выносить в companion object или отдельный файл, чтобы избежать опечаток.
val Context.dataStore by preferencesDataStore(name = "settings")
object SettingsKeys {
val USERNAME = preferencesKey<String>("username")
val DARK_MODE = preferencesKey<Boolean>("dark_mode")
val FONT_SIZE = preferencesKey<Int>("font_size")
}
Запись данных
Для сохранения значения используем метод dataStore.edit, который предоставляет транзакционный доступ к хранилищу. Внутри лямбды через класс MutablePreferences можно записать новое значение, применив оператор [].
suspend fun saveSettings(context: Context, username: String, darkMode: Boolean) {
context.dataStore.edit { preferences ->
preferences[SettingsKeys.USERNAME] = username
preferences[SettingsKeys.DARK_MODE] = darkMode
}
}
Метод suspend, поэтому вызывать его нужно из корутины (например, viewModelScope.launch). Транзакция гарантирует, что запись произойдёт целиком и без конфликтов.
Чтение и подписка на изменения
Для получения значений DataStore возвращает Flow<Preferences>. Чтобы превратить его в удобное состояние для UI, используют map и collectAsState в Compose или asLiveData для классического View. При любом изменении ключа Flow эмитит новое значение.
val usernameFlow: Flow<String> = context.dataStore.data.map { preferences ->
preferences[SettingsKeys.USERNAME] ?: "Гость"
}
В ViewModel этот Flow можно напрямую преобразовать в StateFlow и подписать на него экран.
Пример: экран настроек на Compose
Соберём всё вместе. На экране будет переключатель тёмной темы и поле ввода имени. При изменении данные сразу сохраняются в DataStore, а интерфейс обновляется реактивно.
@Composable
fun SettingsScreen(context: Context) {
val scope = rememberCoroutineScope()
val username by context.dataStore.data.map { prefs ->
prefs[SettingsKeys.USERNAME] ?: ""
}.collectAsState(initial = "")
val darkMode by context.dataStore.data.map { prefs ->
prefs[SettingsKeys.DARK_MODE] ?: false
}.collectAsState(initial = false)
Column(modifier = Modifier.padding(16.dp)) {
OutlinedTextField(
value = username,
onValueChange = { newName ->
scope.launch {
context.dataStore.edit { it[SettingsKeys.USERNAME] = newName }
}
},
label = { Text("Имя пользователя") }
)
Row(verticalAlignment = Alignment.CenterVertically) {
Text("Тёмная тема")
Switch(
checked = darkMode,
onCheckedChange = { checked ->
scope.launch {
context.dataStore.edit { it[SettingsKeys.DARK_MODE] = checked }
}
}
)
}
}
}
Практические рекомендации
- Не смешивайте с SharedPreferences. Если вы мигрируете, перенесите все ключи в DataStore, а старые удалите — двойное хранение усложнит отладку.
- Для сложных объектов используйте Proto DataStore. Он требует описания схемы, но даёт полную типобезопасность, что снижает риск ошибок при сериализации.
- Тестируйте миграцию. DataStore предоставляет метод
SharedPreferencesMigration, который позволяет автоматически перенести данные из старого хранилища при первом запуске.
Чему мы научились
Сегодняшнее занятие в Android Studio помогло освоить современный способ хранения настроек через DataStore. Мы подключили библиотеку, создали ключи, реализовали асинхронную запись и реактивное чтение, а затем собрали готовый экран с помощью языка Kotlin. Теперь ваши приложения смогут надёжно хранить пользовательские параметры, мгновенно реагируя на их изменения без лишней ручной работы. Попробуйте добавить сброс настроек или экспорт — это закрепит понимание библиотеки.