Androidx 首选项库与数据存储首选项
Androidx Preferences Library vs DataStore preferences
我之前按照文档中 Google 的建议,用新的 DataStore 替换了我的应用程序中的 SharedPreferences,以获得一些明显的好处。然后是添加设置屏幕的时候了,我找到了首选项库。当我看到该库默认使用 SharedPreferences 而没有切换到 DataStore 的选项时,我感到很困惑。您可以使用 setPreferenceDataStore
提供自定义存储实现,但 DataStore 没有实现 PreferenceDataStore 接口,留给开发人员。是的,这个命名也非常混乱。当我发现没有任何文章或问题谈论将 DataStore 与 Preferences Library 一起使用时,我变得更加困惑,所以我觉得我错过了什么。人们是否同时使用这两种存储解决方案?或者一个或另一个?如果我要在 DataStore 中实现 PreferenceDataStore,有什么我应该注意的 gotchas/pitfalls 吗?
我在使用 DataStore 时遇到了同样的问题 运行。 DataStore
不仅没有实现 PreferenceDataStore
,而且我认为不可能编写一个适配器来桥接两者,因为 DataStore
使用 Kotlin Flow
s 并且是异步的,而 PreferenceDataStore
假设 get 和 put 操作都是同步的。
我的解决方案是使用回收站视图手动编写首选项屏幕。幸运的是,ConcatAdapter
使它变得更容易,因为我基本上可以为每个首选项创建一个适配器,然后使用 ConcatAdapter
.
将它们组合成一个适配器
我最终得到的是一个 PreferenceItemAdapter
,它具有可变的 title
、summary
、visible
和 enabled
属性,可以模仿首选项库,以及受 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 中存储了一些首选项).
我将添加我自己的解决不兼容性的策略,以防它对某些人有用:
我坚持使用首选项库并将 android:persistent="false"
添加到我所有可编辑的首选项中,因此他们根本不会使用 SharedPreferences
。然后我可以自由地反应性地保存和加载偏好值。通过click/change listeners → view model → repository 存储它们,并用观察者反射回来。
绝对比一个好的自定义解决方案更混乱,但它对我的小应用程序来说效果很好。
对于阅读问题并思考 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)
}
}
但是此解决方案存在一些问题。根据 documentation runBlocking
和 first()
同步读取值是首选方式,但应谨慎使用。
确保在调用 setPreferencesFromResource
之前设置preferenceDataStore
以避免加载问题,因为默认实现 (sharedPreferences) 将用于初始加载。
几周前,在我最初尝试实现 PreferenceDataStore 时,我遇到了类型 long
键的问题。我的设置屏幕正确显示并保存了 EditTextPreference
的数值,但流程没有为这些键发出任何值。 EditTextPreference
将数字保存为字符串可能存在问题,因为在 xml 中设置 inputType 似乎无效(至少在输入键盘上无效)。虽然将数字保存为字符串可能有效,但这也需要将数字读取为字符串。因此,您失去了原始类型的类型安全性。
也许对设置和数据存储库进行一两次更新,可能会有针对这种情况的官方工作解决方案。
我之前按照文档中 Google 的建议,用新的 DataStore 替换了我的应用程序中的 SharedPreferences,以获得一些明显的好处。然后是添加设置屏幕的时候了,我找到了首选项库。当我看到该库默认使用 SharedPreferences 而没有切换到 DataStore 的选项时,我感到很困惑。您可以使用 setPreferenceDataStore
提供自定义存储实现,但 DataStore 没有实现 PreferenceDataStore 接口,留给开发人员。是的,这个命名也非常混乱。当我发现没有任何文章或问题谈论将 DataStore 与 Preferences Library 一起使用时,我变得更加困惑,所以我觉得我错过了什么。人们是否同时使用这两种存储解决方案?或者一个或另一个?如果我要在 DataStore 中实现 PreferenceDataStore,有什么我应该注意的 gotchas/pitfalls 吗?
我在使用 DataStore 时遇到了同样的问题 运行。 DataStore
不仅没有实现 PreferenceDataStore
,而且我认为不可能编写一个适配器来桥接两者,因为 DataStore
使用 Kotlin Flow
s 并且是异步的,而 PreferenceDataStore
假设 get 和 put 操作都是同步的。
我的解决方案是使用回收站视图手动编写首选项屏幕。幸运的是,ConcatAdapter
使它变得更容易,因为我基本上可以为每个首选项创建一个适配器,然后使用 ConcatAdapter
.
我最终得到的是一个 PreferenceItemAdapter
,它具有可变的 title
、summary
、visible
和 enabled
属性,可以模仿首选项库,以及受 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 中存储了一些首选项).
我将添加我自己的解决不兼容性的策略,以防它对某些人有用:
我坚持使用首选项库并将 android:persistent="false"
添加到我所有可编辑的首选项中,因此他们根本不会使用 SharedPreferences
。然后我可以自由地反应性地保存和加载偏好值。通过click/change listeners → view model → repository 存储它们,并用观察者反射回来。
绝对比一个好的自定义解决方案更混乱,但它对我的小应用程序来说效果很好。
对于阅读问题并思考 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)
}
}
但是此解决方案存在一些问题。根据 documentation runBlocking
和 first()
同步读取值是首选方式,但应谨慎使用。
确保在调用 setPreferencesFromResource
之前设置preferenceDataStore
以避免加载问题,因为默认实现 (sharedPreferences) 将用于初始加载。
几周前,在我最初尝试实现 PreferenceDataStore 时,我遇到了类型 long
键的问题。我的设置屏幕正确显示并保存了 EditTextPreference
的数值,但流程没有为这些键发出任何值。 EditTextPreference
将数字保存为字符串可能存在问题,因为在 xml 中设置 inputType 似乎无效(至少在输入键盘上无效)。虽然将数字保存为字符串可能有效,但这也需要将数字读取为字符串。因此,您失去了原始类型的类型安全性。
也许对设置和数据存储库进行一两次更新,可能会有针对这种情况的官方工作解决方案。