简体   繁体   English

Androidx 首选项库与数据存储首选项

[英]Androidx Preferences Library vs DataStore preferences

I had previously replaced SharedPreferences in my app with the new DataStore, as recommended by Google in the docs, to reap some of the obvious benefits.按照 Google 在文档中的建议,我之前已将应用程序中的 SharedPreferences 替换为新的 DataStore,以获得一些明显的好处。 Then it came time to add a settings screen, and I found the Preferences Library.然后是添加设置屏幕的时候了,我找到了 Preferences Library。 The confusion came when I saw the library uses SharedPreferences by default with no option to switch to DataStore.当我看到库默认使用 SharedPreferences 而没有切换到 DataStore 的选项时,我感到困惑。 You can use setPreferenceDataStore to provide a custom storage implementation, but DataStore does not implement the PreferenceDataStore interface, leaving it up to the developer.您可以使用setPreferenceDataStore提供自定义存储实现,但 DataStore 不实现 PreferenceDataStore 接口,由开发人员决定。 And yes this naming is also extremely confusing.是的,这个命名也非常令人困惑。 I became more confused when I found no articles or questions talking about using DataStore with the Preferences Library, so I feel like I'm missing something.当我发现没有关于将 DataStore 与 Preferences Library 一起使用的文章或问题时,我变得更加困惑,所以我觉得我错过了一些东西。 Are people using both of these storage solutions side by side?人们是否并排使用这两种存储解决方案? Or one or the other?还是其中之一? If I were to implement PreferenceDataStore in DataStore, are there any gotchas/pitfalls I should be looking out for?如果我要在 DataStore 中实现 PreferenceDataStore,我应该注意哪些陷阱/陷阱?

For anyone reading the question and thinking about the setPreferenceDataStore -solution.对于任何阅读问题并考虑setPreferenceDataStore -解决方案的人。 Implementing your own PreferencesDataStore with the DataStore instead of SharedPreferences is straight forward at a glance.使用 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() }
    }

    ...
}

And then setting the datastore in your fragment然后在您的片段中设置数据存储

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

But there are a few issues with this solution.但是这个解决方案存在一些问题。 According to the documentation runBlocking with first() to synchronously read values is the preferred way, but should be used with caution.根据文档runBlocking with first()同步读取值是首选方式,但应谨慎使用。

Make sure to set preferenceDataStore before calling setPreferencesFromResource to avoid loading issues where the default implementation (sharedPreferences) will be used for initial loading.确保在调用setPreferencesFromResource之前设置preferenceDataStore以避免使用默认实现 (sharedPreferences) 进行初始加载的加载问题。

A couple weeks ago on my initial try to implement the PreferenceDataStore, I had troubles with type long keys.几周前,在我最初尝试实现 PreferenceDataStore 时,我遇到了键入long键的问题。 My settings screen was correctly showing and saving numeric values for an EditTextPreference but the flows did not emit any values for these keys.我的设置屏幕正确显示并保存了EditTextPreference的数值,但流程没有为这些键发出任何值。 There might be an issue with EditTextPreference saving numbers as strings because setting an inputType in the xml seems to have no effect (at least not on the input keyboard). EditTextPreference将数字保存为字符串可能存在问题,因为在 xml 中设置 inputType 似乎没有效果(至少在输入键盘上没有)。 While saving numbers as strings might work, this also requires reading numbers as strings.虽然将数字保存为字符串可能有效,但这也需要将数字读取为字符串。 Therefore you lose the type-safety for primitive types.因此,您失去了原始类型的类型安全性。

Maybe with one or two updates on the settings and datastore libs there might be an official working solution for this case.也许在设置和数据存储库上进行一两次更新后,可能会有针对这种情况的官方工作解决方案。

I have run into the same issue using DataStore.我在使用 DataStore 时遇到了同样的问题。 Not only does DataStore not implement PreferenceDataStore , but I believe it is impossible to write an adapter to bridge the two, because the DataStore uses Kotlin Flow s and is asynchronous, whereas PreferenceDataStore assumes that both get and put operations to be synchronous. DataStore不仅没有实现PreferenceDataStore ,而且我相信不可能编写一个适配器来桥接两者,因为DataStore使用 Kotlin Flow并且是异步的,而PreferenceDataStore假设 get 和 put 操作都是同步的。

My solution to this is to write the preference screen manually using a recycler view.我对此的解决方案是使用回收站视图手动编写首选项屏幕。 Fortunately, ConcatAdapter made it much easier, as I can basically create one adapter for each preference item, and then combine them into one adapter using ConcatAdapter .幸运的是, ConcatAdapter让它变得更加容易,因为我基本上可以为每个首选项创建一个适配器,然后使用ConcatAdapter将它们组合成一个适配器。

What I ended up with is a PreferenceItemAdapter that has mutable title , summary , visible , and enabled properties that mimics the behavior of the preference library, and also a Jetpack Compose inspired API that looks like this:我最终得到的是一个PreferenceItemAdapter ,它具有可变的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 } }
      }
    }
  }
}

There is more manual code in this approach, but I find it easier than trying to bend the preference library to my will, and gives me the flexibility I needed for my project (which also stores some of the preferences in Firebase).这种方法有更多的手动代码,但我发现它比尝试根据自己的意愿弯曲首选项库更容易,并为我的项目提供了所需的灵活性(它还在 Firebase 中存储了一些首选项)。

I'll add my own strategy I went with for working around the incompatibility in case it's useful to some:我将添加我自己的策略来解决不兼容问题,以防它对某些人有用:

I stuck with the preference library and added android:persistent="false" to all my editable preferences so they wouldn't use SharedPreferences at all.我坚持使用首选项库,并将android:persistent="false"添加到我所有的可编辑首选项中,这样他们就根本不会使用SharedPreferences Then I was free to just save and load the preference values reactively.然后我可以自由地保存和响应性地加载首选项值。 Storing them through click/change listeners → view model → repository, and reflecting them back with observers.通过单击/更改侦听器存储它们→查看 model→存储库,并将它们反映给观察者。

Definitely messier than a good custom solution, but it worked well for my small app.绝对比一个好的自定义解决方案更混乱,但它适用于我的小应用程序。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM