DataStore: що це, основи зберігання даних і заміна SharedPreferences

Автор: IT Sectr Опубліковано: 2026-03-12 Час читання: 9 хв

DataStore — компонент з бібліотеки Jetpack, призначений для зберігання невеликих обсягів даних в Android-додатках. На відміну від SharedPreferences, він працює асинхронно і гарантує узгодженість даних при конкурентному доступі. За даними Google, 2024, DataStore використовує Kotlin Coroutines і Flow, що робить його безпечним для головного потоку і придатним для реактивних архітектур.

Головне

  • DataStore — заміна SharedPreferences з асинхронним API та типами даних через Protocol Buffers
  • Preferences DataStore — простий Key-Store з читанням через Flow та транзакціями
  • Proto DataStore — типізоване сховище з автоматичними міграціями схеми
  • SharedPreferences — синхронне API, що блокує UI-потік при великих обсягах
  • Міграція виконується через спеціальний інтерфейс SharedPreferencesMigration без втрати даних

Що таке DataStore?

DataStore — рішення від Google для локального зберігання даних в Android, представлене в 2020 році як альтернатива SharedPreferences. Він підтримує два режими: Preferences DataStore (прості пари ключ-значення) і Proto DataStore (типізована схема на основі Protocol Buffers).

Основна перевага — повна асинхронність: всі операції читання повертають Flow з Kotlin Coroutines, а запис виконується в coroutine-контексті. Це виключає блокування головного потоку, яка була типовою проблемою SharedPreferences при роботі з великими обсягами даних.

DataStore гарантує атомарність операцій: конкурентні записи не призводять до втрати даних завдяки транзакційній моделі. Якщо два компоненти одночасно змінюють те саме значення, DataStore коректно обробляє конфлікт через механізм compare-and-swap.

За даними Google I/O 2023, DataStore використовується в 40% нових проектів на Android, і Google рекомендує мігрувати з SharedPreferences у всіх додатках, де потрібна стабільність зберігання налаштувань.

Архітектура DataStore

В основі DataStore лежить SingleProcessDataStore — реалізація, що працює в межах одного процесу. Вона використовує файлове сховище з блокуваннями на рівні файлу: при записі даних файл блокується, що запобігає пошкодженню при конкурентному доступі.

DataStore автоматично обробляє помилки десеріалізації: якщо файл пошкоджено, він повертає значення за замовчуванням і перезаписує файл. Ця поведінка налаштовується через corruptionHandler, який можна задати при створенні DataStore.

Проблеми SharedPreferences, які вирішує DataStore

SharedPreferences страждає від трьох фундаментальних проблем: синхронне читання з диска на головному потоці, відсутність гарантій атомарності при конкурентних записах і неможливість реактивного відстеження змін. DataStore вирішує всі три: Flow для спостереження, файлове блокування для атомарності та асинхронне API для безпеки потоків.

Як працює DataStore в Android?

DataStore зберігає дані у файлах на внутрішньому сховищі пристрою. Preferences DataStore використовує формат файлу, аналогічний SharedPreferences, але з додатковими метаданими для перевірки цілісності. Proto DataStore використовує бінарний формат Protocol Buffers, що зменшує розмір файлу і прискорює серіалізацію.

При читанні даних DataStore завантажує весь файл у пам'ять одноразово, після чого підписники отримують актуальний стан через Flow. Зміни транслюються всім активним підписникам автоматично — не потрібна ручна реєстрація listener-ів, як у SharedPreferences.

Принцип роботи Preferences DataStore

Preferences DataStore використовує вбудований механізм серіалізації на основі Map. Кожен запис — пара рядка і примітивного типу (Int, Boolean, Float, Long, String, Set). Дані зберігаються у XML-файлі, аналогічному SharedPreferences, але з атомарним записом через файлове блокування.

Приклад створення Preferences DataStore: розширення preferencesDataStore на Context створює синглтон з іменем файлу. При повторних викликах повертається той самий екземпляр — це виключає дублювання файлів і плутанину з різними екземплярами сховища.

Принцип роботи Proto DataStore

Proto DataStore вимагає визначення схеми даних через .proto-файл і компіляції за допомогою protobuf-плагіна. Згенерований Java-клас використовується як єдина точка входу для всіх полів — це виключає помилки в ключах, типові для SharedPreferences.

Схема Proto DataStore задається один раз і підтримує додавання нових полів без втрати старих даних. Якщо в новій версії додатка додати поле з default-значенням, старий файл буде коректно десеріалізовано — зворотна сумісність вбудована в протокол.

Preferences DataStore і Proto DataStore: порівняння

Вибір між Preferences DataStore і Proto DataStore залежить від складності даних і вимог до типізації. Обидва варіанти асинхронні і транзакційні, але розрізняються за рівнем type-safety і продуктивністю серіалізації.

ХарактеристикаPreferences DataStoreProto DataStore
ТипізаціяСлабка (ключ-значення)Сильна (згенерований клас)
СеріалізаціяXML (вбудована)Protocol Buffers (protobuf)
Розмір файлуВеликий (читабельний XML)Малий (бінарний)
СкладністьНизька (без .proto)Середня (потрібен .proto)
Міграція схемиНемає схемиАвтоматична (proto)
СумісністьSharedPreferences (через міграцію)Тільки Proto DataStore

Коли вибирати Preferences DataStore

Preferences DataStore підходить для простих налаштувань: прапорці включення функцій, рядок токена авторизації, кількість запусків додатка. Якщо даних мало (до 10–15 ключів) і вони не вимагають строгої схеми — Preferences DataStore дає мінімальний поріг входу без підключення protobuf-плагіна.

Коли вибирати Proto DataStore

Proto DataStore виправданий, коли структура даних складна або може змінюватися між версіями додатка. Наприклад, налаштування профілю користувача або конфігурація A/B-тестів з 20+ полями. Protobuf дає строгу типізацію та автоматичні міграції, що виключає помилки часу виконання через неспівпадіння ключів.

Як мігрувати з SharedPreferences на DataStore

Google надає вбудований механізм міграції через клас SharedPreferencesMigration. Міграція виконується одноразово при першому запуску після оновлення додатка: DataStore читає дані з SharedPreferences, записує їх у свій формат і позначає міграцію завершеною.

Міграція підтримує кастомні трансформації: якщо в SharedPreferences ключі не збігаються з бажаними ключами DataStore, можна задати функцію перетворення через SharedPreferencesMigration. Це дозволяє перейменовувати ключі та змінювати типи даних у процесі міграції.

Покрокова міграція

Першим кроком додайте DataStore в build.gradle і створіть екземпляр DataStore з міграцією: SharedPreferencesMigration приймає ім'я SharedPreferences-файлу та набір ключів, які потрібно перенести. Другим кроком видаліть весь код, що працює через SharedPreferences, і замініть його викликами DataStore. Третім — протестуйте міграцію: при першому запуску дані повинні з'явитися в DataStore, а старий SharedPreferences-файл — перестати використовуватися.

kotlin
val Context.dataStore by preferencesDataStore(
    name = "settings",
    produceMigrations = { context ->
        listOf(
            SharedPreferencesMigration(context, "old_prefs")
        )
    }
)

Приклади використання DataStore в коді

DataStore легко інтегрується в існуючий проект. Нижче наведені практичні приклади для Preferences DataStore і Proto DataStore — обидва демонструють читання, запис і реактивне спостереження за даними.

Preferences DataStore: читання і запис налаштувань

У цьому прикладі Preferences DataStore зберігає три налаштування: темну тему, ім'я користувача та кількість запусків. Читання виконується через розширення .data, яке повертає Flow. Запис — через suspend-функцію .edit, що гарантує атомарність змін.

kotlin
val Context.settingsDataStore by preferencesDataStore(name = "settings")

val isDarkMode: Flow<Boolean> = settingsDataStore.data
    .map { preferences ->
        preferences[booleanPreferencesKey("dark_mode")] ?: false
    }

suspend fun toggleDarkMode() {
    settingsDataStore.edit { prefs ->
        val current = prefs[booleanPreferencesKey("dark_mode")] ?: false
        prefs[booleanPreferencesKey("dark_mode")] = !current
    }
}

Proto DataStore: схема і використання

Proto DataStore вимагає визначення .proto-файлу. Після компіляції створюється клас UserSettings, який використовується для читання і запису. Міграції версій схеми описуються в тому ж .proto-файлі та застосовуються автоматично.

kotlin
// user_preferences.proto
syntax = "proto3";

message UserPreferences {
    string display_name = 1;
    int32 notification_count = 2;
    bool notifications_enabled = 3;
}

// Читання з DataStore
val userPreferencesFlow: Flow<UserPreferences> =
    protoDataStore.data

// Запис нових значень
suspend fun updateDisplayName(name: String) {
    protoDataStore.updateData { prefs ->
        prefs.toBuilder()
            .setDisplayName(name)
            .build()
    }
}

Реактивне спостереження за змінами

DataStore інтегрується з архітектурою MVVM через ViewModel. Flow з DataStore збирається через .stateIn і використовується в UI. При кожній зміні даних UI оновлюється автоматично — не потрібні ручні оновлення або LiveData.

kotlin
class SettingsViewModel(
    private val dataStore: DataStore<Preferences>
) : ViewModel() {

    val uiState: StateFlow<SettingsUiState> =
        dataStore.data
            .map { prefs ->
                SettingsUiState(
                    isDarkMode = prefs[booleanPreferencesKey("dark_mode")] ?: false,
                    counter = prefs[intPreferencesKey("launch_count")] ?: 0
                )
            }
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5000),
                initialValue = SettingsUiState()
            )
}

Часто задавані питання

Чим DataStore кращий за SharedPreferences?

DataStore працює асинхронно (не блокує UI-потік), підтримує конкурентний доступ через транзакції та дозволяє реактивно підписуватися на зміни через Flow. SharedPreferences — синхронне API з ризиком ANR при великих обсягах даних і без вбудованої підтримки реактивності.

Чи можна використовувати DataStore з Java?

DataStore написаний на Kotlin і вимагає Kotlin Coroutines. Використовувати його з Java можна, але це незручно: доведеться створювати обгортки з CompletableFuture або вручну керувати корутинами. Для Java-проектів Google рекомендує залишити SharedPreferences або додати Kotlin в модуль.

DataStore підходить для зберігання великих обсягів даних?

DataStore завантажує весь файл у пам'ять при читанні, тому не підходить для зберігання списків або великих об'єктів. Для таких сценаріїв використовуйте Room або SQLite. DataStore оптимізований для налаштувань і невеликих структурованих даних — до сотень кілобайт.

Як обробити помилку пошкодженого файлу DataStore?

При створенні DataStore можна передати corruptionHandler — функцію, яка викликається при пошкодженні файлу. За замовчуванням DataStore викидає виняток CorruptionException. У corruptionHandler можна повернути порожні дані, після чого DataStore перезапише файл коректним станом.

Proto DataStore вимагає обов'язкового .proto-файлу?

Так, Proto DataStore вимагає визначення схеми в .proto-файлі та підключення плагіна protobuf-gradle-plugin. Якщо проект невеликий і дані прості, простіше використовувати Preferences DataStore — він не вимагає додаткової конфігурації збірки.

Підсумки

  • DataStore — сучасна заміна SharedPreferences, що працює з Kotlin Coroutines і Flow
  • Preferences DataStore — простий ключ-значення без схеми, підходить для налаштувань
  • Proto DataStore — типізоване сховище з protobuf-схемою та авто-міграцією
  • Міграція з SharedPreferences вбудована в DataStore через SharedPreferencesMigration
  • Безпека потоків — всі операції асинхронні, блокування UI виключено
  • Реактивність — Flow сповіщає підписників при кожній зміні даних
  • Рекомендація — використовуйте DataStore у всіх нових проектах Android, мігруйте існуючі при роботі з налаштуваннями

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також