13

按照 Google 在文档中的建议,我之前已将应用程序中的 SharedPreferences 替换为新的 DataStore,以获得一些明显的好处。然后是添加设置屏幕的时候了,我找到了 Preferences Library。当我看到库默认使用 SharedPreferences 而没有切换到 DataStore 的选项时,我感到困惑。您可以使用setPreferenceDataStore提供自定义存储实现,但 DataStore 没有实现 PreferenceDataStore 接口,由开发人员决定。是的,这个命名也非常令人困惑。当我发现没有关于将 DataStore 与 Preferences Library 一起使用的文章或问题时,我变得更加困惑,所以我觉得我错过了一些东西。人们是否并排使用这两种存储解决方案?还是其中之一?如果我要在 DataStore 中实现 PreferenceDataStore,我应该注意哪些陷阱/陷阱?

4

3 回答 3

7

对于任何阅读问题并考虑解决方案的setPreferenceDataStore人。使用 DataStore 而不是 SharedPreferences 实现您自己的 PreferencesDataStore 一目了然。

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)
    }
}

但是这个解决方案存在一些问题。根据文档 runBlockingfirst()同步读取值是首选方式,但应谨慎使用。

确保preferenceDataStore在调用之前设置setPreferencesFromResource以避免加载问题,其中默认实现(sharedPreferences)将用于初始加载。

几周前,在我最初尝试实现 PreferenceDataStore 时,我遇到了类型long键的问题。我的设置屏幕正确显示并保存了 a 的数值,EditTextPreference但流程没有为这些键发出任何值。将数字保存为字符串可能存在问题,EditTextPreference因为在 xml 中设置 inputType 似乎没有效果(至少在输入键盘上没有)。虽然将数字保存为字符串可能有效,但这也需要将数字读取为字符串。因此,您失去了原始类型的类型安全性。

也许在设置和数据存储库上进行一两次更新后,可能会有针对这种情况的官方工作解决方案。

于 2021-02-11T23:07:13.213 回答
4

我在使用 DataStore 时遇到了同样的问题。不仅DataStore没有实现PreferenceDataStore,而且我相信不可能编写一个适配器来桥接两者,因为它DataStore使用 KotlinFlow并且是异步的,而PreferenceDataStore假设 get 和 put 操作都是同步的。

我对此的解决方案是使用回收站视图手动编写首选项屏幕。幸运的是,ConcatAdapter它变得更容易了,因为我基本上可以为每个首选项创建一个适配器,然后使用ConcatAdapter.

我最终得到的是一个PreferenceItemAdapter具有 mutable titlesummaryvisibleenabled模仿首选项库行为的属性,以及一个受 Jetpack Compose 启发的 API,如下所示:

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 中存储了一些首选项)。

于 2021-01-13T09:12:05.380 回答
1

我将添加我自己的策略来解决不兼容问题,以防它对某些人有用:

我坚持使用首选项库并添加android:persistent="false"到我所有的可编辑首选项中,这样它们根本就不会使用SharedPreferences。然后我可以自由地保存和响应性地加载首选项值。通过单击/更改侦听器 → 视图模型 → 存储库存储它们,并通过观察者反映它们。

绝对比一个好的自定义解决方案更混乱,但它适用于我的小应用程序。

于 2021-01-15T19:41:12.127 回答