Что такое DataStore
На протяжении многих лет разработчики Android хранили небольшие фрагменты конфиденциальных пользовательских данных с помощью общих настроек (shared preferences). Этот подход имеет следующие недостатки:
- Конфиденциальные данные в общих настройках могут быть легко раскрыты.
- Кажется, что вызывать операции с общими настройками в потоке UI безопасно, но это не так (из-за синхронного API, отсутствия механизма сигнализации ошибок, нехватки транзакционного API и т.д.).
DataStore — это библиотека из семейства Jetpack, которая предоставляет новое решение для хранения данных. Скорее всего, оно заменит общие настройки. В настоящее время библиотека находится в альфа-стадии.
Какие подходы она предлагает
Библиотека разработана с использованием корутин Kotlin и Flow API, что делает её более безопасной и надежной, чем общие настройки. Предлагаются два различных подхода к хранению данных:
- Preferences DataStore. Они похожи на
SharedPreferences
, поскольку не могут определить схему или обеспечить доступ к ключам с правильным типом. - Proto DataStore. С ее помощью можно создавать схемы, используя буферы протокола, которые позволяют сохранять строго типизированные данные. Они быстрее, меньше, проще и менее неоднозначны, чем XML и другие подобные форматы данных.
Кроме того, при хранении пар ключ-значение будут доступны только ключи, а не само содержимое.
DataStore при работе с базами данных
DataStore предназначена для хранения небольших наборов данных. Если в ваши требования входит частичное обновление, ссылочная целостность или поддержка больших и сложных наборов данных, стоит рассмотреть использование Room.
Интеграция DataStore
Чтобы использовать библиотеку Jetpack DataStore, добавьте строку под узлом dependencies
в файле build.gradle
уровня app.
implementation "androidx.datastore:datastore-preferences:1.0.0-alpha01"
Использование библиотеки на примере
Чтобы лучше понять, как использовать DataStore, я взял простой пример в реальном времени, где мы храним целое число, указывающее статус входа пользователя. Здесь у нас есть класс enum
со всеми возможными этапами:
enum class UserStatus {
STARTER, ONBOARDING_LEVEL_1, ONBOARDING_LEVEL_2, VERIFIED
}
Создание DataStore
Следующим шагом является создание DataStore. Для этого нужно создать класс Kotlin, в котором мы будем использовать функцию расширения context.createDataStore
:
class PreferenceManager(context: Context){
private val dataStore = context.createDataStore(name = "prefernce_name")
}
Создание ключей
Создадим встроенную функцию preferencesKey
, определив тип результата:
companion object {
val USER_STATUS = preferencesKey<Int>("user_status")
}
Сохранение и извлечение данных
Теперь, когда мы создали DataStore и ключи, пришло время сохранить статус пользователя. Сохраним целое число со значениями 1, 2, 3 и 4, представляющими STARTER
, ONBOARDING_LEVEL_1
, ONBOARDING_LEVEL_2
и VERIFIED
.
Чтобы сохранить пары ключ-значение в файле DataStore, воспользуемся функцией edit
, которая обновляет значения и suspend
для их сохранения:
suspend fun setUserStatus(userStatus: UserStatus) {
dataStore.edit { preferences ->
preferences[USER_STATUS] = when (userStatus) {
UserStatus.STARTER -> 1
UserStatus.ONBOARDING_LEVEL_1 -> 2
UserStatus.ONBOARDING_LEVEL_2 -> 3
UserStatus.VERIFIED -> 4
}
}
}
Чтобы извлечь значения из DataStore, мы используем Flow API. Одно из главных преимуществ этого подхода в том, что каждый раз, когда в DataStore обновляется значение, мы получаем уведомление. Таким образом, проверять обновленные значения не потребуется.
val userStatusFlow: Flow<UserStatus> = dataStore.data
.catch {
if (it is IOException) {
it.printStackTrace()
emit(emptyPreferences())
} else {
throw it
}
}
.map { preference ->
val userStatus = preference[USER_STATUS] ?: 1
when (userStatus) {
1 -> UserStatus.STARTER
2 -> UserStatus.ONBOARDING_LEVEL_1
3 -> UserStatus.ONBOARDING_LEVEL_2
4 -> UserStatus.VERIFIED
else -> UserStatus.STARTER
}
}
Вместо того, чтобы передавать целое число в регистр состояния пользователя на сайте вызова, можно использовать такие функции, как map
, для преобразования данных в соответствующий тип. Соберем все воедино:
enum class UserStatus {
STARTER, ONBOARDING_LEVEL_1, ONBOARDING_LEVEL_2, VERIFIED
}
class PreferenceManager(context: Context) {
private val dataStore = context.createDataStore(name = "prefernce_name")
val userStatusFlow: Flow<UserStatus> = dataStore.data
.catch {
if (it is IOException) {
it.printStackTrace()
emit(emptyPreferences())
} else {
throw it
}
}
.map { preference ->
val userStatus = preference[USER_STATUS] ?: 1
when (userStatus) {
1 -> UserStatus.STARTER
2 -> UserStatus.ONBOARDING_LEVEL_1
3 -> UserStatus.ONBOARDING_LEVEL_2
4 -> UserStatus.VERIFIED
else -> UserStatus.STARTER
}
}
suspend fun setUserStatus(userStatus: UserStatus) {
dataStore.edit { preferences ->
preferences[USER_STATUS] = when (userStatus) {
UserStatus.STARTER -> 1
UserStatus.ONBOARDING_LEVEL_1 -> 2
UserStatus.ONBOARDING_LEVEL_2 -> 3
UserStatus.VERIFIED -> 4
}
}
}
companion object {
val USER_STATUS = preferencesKey<Int>("user_status")
}
}
Создание доступа из компонентов Android
Теперь, когда мы закончили с менеджером настроек, разберемся, как вызвать setUserStatus
из активности Android:
fun saveuserStatus(status : Int){
lifecycleScope.launch {
when (status) {
1 -> preferenceManager.setUserStatus(UserStatus.STARTER)
2 -> preferenceManager.setUserStatus(UserStatus.ONBOARDING_LEVEL_1)
3 -> preferenceManager.setUserStatus(UserStatus.ONBOARDING_LEVEL_2)
4 -> preferenceManager.setUserStatus(UserStatus.VERIFIED)
}
}
}
Поскольку setUserStatus
— это функция приостановки, для запуска сопрограммы мы использовали lifecycleScope
.
Извлечение данных из центра обработки аналогично: здесь мы применяем collectLatest
из Flow API, деформируя его с помощью lifecycleScope
:
lifecycleScope.launch(Dispatchers.Main) {
preferencemanager.userStatusFlow.collectLatest { userStatus->
when (userStatus) {
UserStatus.STARTER -> {/* TODO */}
UserStatus.ONBOARDING_LEVEL_1 -> {/* TODO */}
UserStatus.ONBOARDING_LEVEL_2 -> {/* TODO */}
UserStatus.VERIFIED -> {/* TODO */}
}
}
}
Читайте также:
- Советы по модуляризации приложений Android
- 8 частых ошибок в Android-разработке
- Clean Architecture в Android для начинающих
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Siva Ganesh Kantamani: Jetpack DataStore: Improved Data-Storage System