问题描述
我之前按照 Google 在文档中的建议,用新的 DataStore 替换了我应用中的 SharedPreferences,以获得一些明显的好处。然后是添加设置屏幕的时候了,我找到了首选项库。当我看到库默认使用 SharedPreferences 而没有切换到 DataStore 的选项时,困惑就来了。您可以使用 setPreferenceDataStore
提供自定义存储实现,但 DataStore 不实现 PreferenceDataStore 接口,这由开发人员决定。是的,这个命名也非常令人困惑。当我发现没有关于将 DataStore 与 Preferences Library 一起使用的文章或问题时,我变得更加困惑,所以我觉得我错过了一些东西。人们是否同时使用这两种存储解决方案?还是其中之一?如果我要在 DataStore 中实现 PreferenceDataStore,有什么我应该注意的问题/陷阱吗?
解决方法
对于阅读问题并思考 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()
同步读取值是首选方式,但应谨慎使用。
确保在调用 preferenceDataStore
之前设置 setPreferencesFromResource
以避免加载问题,其中默认实现 (sharedPreferences) 将用于初始加载。
几周前,在我最初尝试实现 PreferenceDataStore 时,我遇到了 long
类型键的问题。我的设置屏幕正确显示并保存了 EditTextPreference
的数值,但流没有为这些键发出任何值。 EditTextPreference
将数字保存为字符串可能存在问题,因为在 xml 中设置 inputType 似乎没有效果(至少在输入键盘上没有效果)。虽然将数字保存为字符串可能有效,但这也需要将数字作为字符串读取。因此,您失去了原始类型的类型安全性。
也许对设置和数据存储库进行一两次更新,可能会有针对这种情况的官方工作解决方案。
,我在使用 DataStore 时遇到了同样的问题。不仅 DataStore
没有实现 PreferenceDataStore
,而且我认为编写一个适配器来桥接两者是不可能的,因为 DataStore
使用 Kotlin Flow
并且是异步的,而 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
。然后我可以自由地保存和加载偏好值。通过单击/更改侦听器 → 视图模型 → 存储库存储它们,并通过观察者将它们反射回来。
绝对比一个好的自定义解决方案更混乱,但它对我的小应用程序运行良好。