Библиотека настроек Androidx против настроек хранилища данных

Ранее я заменил SharedPreferences в своем приложении на новый DataStore, как это рекомендовано Google в документации, чтобы воспользоваться некоторыми очевидными преимуществами. Затем пришло время добавить экран настроек, и я нашел библиотеку настроек. Путаница возникла, когда я увидел, что библиотека по умолчанию использует SharedPreferences без возможности переключения на DataStore. Вы можете использовать setPreferenceDataStore для предоставления пользовательской реализации хранилища, но DataStore не реализует интерфейс PreferenceDataStore, оставляя это на усмотрение разработчика. И да, это наименование также чрезвычайно запутанно. Я еще больше запутался, когда не нашел статей или вопросов об использовании DataStore с библиотекой настроек, поэтому мне кажется, что я что-то упускаю. Используют ли люди оба этих решения для хранения одновременно? Или и то, и другое? Если бы я реализовал PreferenceDataStore в DataStore, есть ли какие-то подводные камни, на которые мне следует обратить внимание?

Возможно, это поможет вам: medium.com/swlh/…

Dr.jacky 08.02.2021 12:19

Это такой облом, что пока я просто буду придерживаться старых общих настроек. Старый API по-прежнему работает нормально, не устарел и экономит мне время и кодовую базу для написания представления предпочтений и связывания его с серверным хранилищем.

Constructor 30.01.2022 13:54
17
2
2 855
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Я столкнулся с той же проблемой, используя DataStore. Мало того, что DataStore не реализует PreferenceDataStore, но я считаю, что невозможно написать адаптер для их соединения, потому что DataStore использует Kotlin Flows и является асинхронным, тогда как PreferenceDataStore предполагает, что операции get и put должны быть синхронными.

Мое решение состоит в том, чтобы написать экран предпочтений вручную, используя представление ресайклера. К счастью, ConcatAdapter сделал это намного проще, так как я могу создать один адаптер для каждого элемента предпочтений, а затем объединить их в один адаптер с помощью ConcatAdapter.

В итоге я получил PreferenceItemAdapter с изменяемыми свойствами title, summary, visible и enabled, которые имитируют поведение библиотеки настроек, а также API, вдохновленный Jetpack Compose, который выглядит следующим образом:

preferenceGroup {
  preference {
    title("Name")
    summary(datastore.data.map { it.name })
    onClick {
      showDialog {
        val text = editText(datastore.data.first().name)
        negativeButton()
        positiveButton()
          .onEach { dataStore.edit { settings -> settings.name = text.first } }.launchIn(lifecycleScope)
      }
    }
  }
  preference {
    title("Date")
    summary(datastore.data.map { it.parsedDate?.format(dateFormatter) ?: "Not configured" })
    onClick {
      showDatePickerDialog(datastore.data.first().parsedDate ?: LocalDate.now()) { newDate ->
        lifecycleScope.launch { dataStore.edit { settings -> settings.date = newDate } }
      }
    }
  }
}

В этом подходе больше ручного кода, но мне кажется, что это проще, чем пытаться согнуть библиотеку настроек по своей воле, и дает мне гибкость, необходимую для моего проекта (который также хранит некоторые настройки в Firebase).

Да, заметил ту же проблему, когда пытался реализовать PreferenceDataStore, но забыл обновить здесь. Я соглашусь с этим, так как ясно, что это невозможно.

fafcrumb 15.01.2021 20:28

Это нелепо. Google необходимо перенести настройки, чтобы использовать новый DataStore API. Это не должно быть поставлено на разработчиков.

Kris B 24.08.2021 16:48

Я добавлю свою собственную стратегию, которую я использовал для обхода несовместимости, на случай, если она кому-то пригодится:

Я придерживался библиотеки настроек и добавил android:persistent = "false" ко всем своим редактируемым настройкам, чтобы они вообще не использовали SharedPreferences. Затем я мог просто сохранять и реактивно загружать значения предпочтений. Сохранение их через прослушиватели кликов/изменений → модель просмотра → репозиторий и их отражение обратно с помощью наблюдателей.

Определенно сложнее, чем хорошее пользовательское решение, но оно хорошо сработало для моего небольшого приложения.

Для всех, кто читает вопрос и думает о setPreferenceDataStore-решении. Реализация собственного PreferencesDataStore с помощью DataStore вместо SharedPreferences выполняется с первого взгляда.

class SettingsDataStore(private val dataStore: DataStore<Preferences>): PreferenceDataStore() {

    override fun putString(key: String, value: String?) {
        CoroutineScope(Dispatchers.IO).launch {
            dataStore.edit {  it[stringPreferencesKey(key)] = value!! }
        }
    }

    override fun getString(key: String, defValue: String?): String {
        return runBlocking { dataStore.data.map { it[stringPreferencesKey(key)] ?: defValue!! }.first() }
    }

    ...
}

И затем установка хранилища данных в вашем фрагменте

@AndroidEntryPoint
class AppSettingsFragment : PreferenceFragmentCompat() {

    @Inject
    lateinit var dataStore: DataStore<Preferences>

    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
        preferenceManager.preferenceDataStore = SettingsDataStore(dataStore)
        setPreferencesFromResource(R.xml.app_preferences, rootKey)
    }
}

Но есть несколько проблем с этим решением. Согласно документации runBlocking с first() синхронное чтение значений является предпочтительным способом, но его следует использовать с осторожностью.

Обязательно установите preferenceDataStore перед вызовом setPreferencesFromResource, чтобы избежать проблем с загрузкой, когда для начальной загрузки будет использоваться реализация по умолчанию (sharedPreferences).

Пару недель назад при первой попытке реализовать PreferenceDataStore у меня возникли проблемы с ключами типа long. Мой экран настроек правильно отображал и сохранял числовые значения для EditTextPreference, но потоки не выдавали никаких значений для этих ключей. Может возникнуть проблема с EditTextPreference сохранением чисел в виде строк, потому что установка inputType в xml, по-видимому, не имеет никакого эффекта (по крайней мере, не на клавиатуре ввода). Хотя сохранение чисел в виде строк может работать, это также требует чтения чисел в виде строк. Поэтому вы теряете безопасность типов для примитивных типов.

Возможно, с одним или двумя обновлениями настроек и библиотек хранилища данных может появиться официальное рабочее решение для этого случая.

Это так глупо. Почему бы Google не создать способ использования DataStore с настройками?

Kris B 19.01.2022 15:43

Суть полной реализации PreferenceDataStore: gist.github.com/Pittvandewitt/c7c620412ec246e34824647a3a4605‌​d0

Thomas W. 21.04.2023 10:29

Другие вопросы по теме