Android Studio: уроки на Kotlin. Хранение настроек с DataStore

Работа с настройками — неотъемлемая часть любого приложения. И если 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. Теперь ваши приложения смогут надёжно хранить пользовательские параметры, мгновенно реагируя на их изменения без лишней ручной работы. Попробуйте добавить сброс настроек или экспорт — это закрепит понимание библиотеки.