在Kotlin中开始使用偏好数据存储(Preference DataStore)
DataStore是谷歌改进的数据存储解决方案,用于取代SharedPreferences,以持久化简单的数据片段,如键值对或带有协议缓冲区的类型化对象。
在本教程中,我们将学习Jetpack DataStore如何工作。我们将研究如何改变一个应用程序的用户界面模式。
简介
Datastore使用Kotlin coroutines和flow来异步、一致、事务性地存储数据,并处理数据损坏。它能很好地处理小型简单数据集。
如果你正在处理大型/复杂的数据集,可以考虑使用Room。这样你就不必担心参照系的完整性或部分更新。
前提条件
- 在Android Studio中创建项目。
- 对Kotlin有良好的理解(因为我们将使用它作为我们的主要语言)。
将要涵盖的主题
- 什么是Jetpack数据存储。
- 数据存储和SharedPreferences之间的主要区别。
- 如何实现数据存储的偏好。
从SharedPreferences转变
SharedPreferences在开发者中很常见,但人们正在寻找更好的解决方案来存储数据,这些解决方案更加强大和高效。它有一些缺点,使其工作起来有点复杂。
如果你使用过Sharedpreferences,你可能已经在你的应用程序上得到了ANR(应用程序无响应)。最常见的原因是主UI线程上的长期运行的任务。
这是因为在使用应用程序时,你试图在应用程序启动后立即访问某个特定键的值。有了这些,你必须访问Sharedpreferences来读取整个文件,不管它有多大,把数据带到内存中,而这一切都发生在UI线程上。
为什么是数据存储?
- 它使用键值对来存储简单的数据。
- 可以安全地从UI线程中调用,因为工作被转移到Dispatchers.IO中。
- 它是安全的,不会出现运行时异常。
- 处理数据迁移。
- 拥有具有强大一致性保证的事务性API。
数据存储提供了两种实现方式
- Preference DataStore - 存储键值对。它与Sharedpreferences相当相似
- Proto DataStore - 存储类型化的对象。这是通过将数据存储为一个自定义数据类型的实例。
少说话,给我看代码
我们将把Jetpack数据存储添加到一个项目中,以改变UI模式,即从亮到暗。
第1步:添加依赖性
在你的应用层的build.gradle中添加以下依赖。
// Preference DataStore
implementation "androidx.datastore:datastore-preferences:1.0.0-alpha04"
其他重要的依赖项。
// Architectural Components
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.4.0'
// Coroutine Lifecycle Scopes
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
我们将在后面看到所有这些在应用程序中的作用。
确保你使用的是这些依赖中的每一个稳定版本的最新版本。
该应用程序是一个简单的应用程序。我们将通过改变它的用户界面模式来处理一个预先存在的应用程序。
第2步:创建一个偏好管理器
创建一个名为UIModePreference的类。这个类持有我们将用来从数据存储中读写数据的代码。
package com.carolmusyoka.noteapp.data.datastore
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.preferencesKey
import androidx.datastore.preferences.createDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
class UIModePreference(context: Context) {
//1
private val dataStore: DataStore<Preferences> = context.createDataStore(
name = "ui_mode_preference"
)
//2
suspend fun saveToDataStore(isNightMode: Boolean) {
dataStore.edit { preferences ->
preferences[UI_MODE_KEY] = isNightMode
}
}
//3
val uiMode: Flow<Boolean> = dataStore.data
.map { preferences ->
val uiMode = preferences[UI_MODE_KEY] ?: false
uiMode
}
//4
companion object {
private val UI_MODE_KEY = preferencesKey<Boolean>("ui_mode")
}
}
代码解释
创建数据存储
第1行基本上是使用文件名"ui_mode_preference" 来创建一个数据存储。createDataStore() 函数是在Context上创建的扩展函数。
写入数据存储
Preference DataStore提供了.edit() 函数,以方便更新数据。这个方法将保存我们活动中的UI模式。
从数据存储中读取数据
DataStore将存储的数据暴露在偏好对象中的一个Flow ,每当偏好被更新时,该对象将发出数值。它还确保数据在Dispatcher.IO 。我们使用map{} ,因为我们正在映射布尔值(记住我们在数据存储中存储布尔值)。
使用一个键来存储偏好
我们已经创建了一个键UI_MODE_KEY ,它将存储光明或黑暗模式的布尔值。Preferences.preferencesKey()为你需要存储在数据存储中的每个值定义了一个键。DataStore<Preferences>
第3步:创建ViewModel类
创建一个新的ViewModel类,名为UIViewModel。
class UIViewModel(application: Application):
AndroidViewModel(application){
private val uiDataStore = UIModePreference(application)
// 1
val getUIMode = uiDataStore.uiMode
// 2
fun saveToDataStore(isNightMode: Boolean) {
viewModelScope.launch(Dispatchers.IO) {
uiDataStore.saveToDataStore(isNightMode)
}
}
}
从数据存储中读取
第1行从数据存储中获取UI模式。
从数据存储中写入
由于从datastore偏好类中的saveToDataStore() 是一个挂起的函数,它只能从一个coroutine范围内调用。
这就是为什么我们使用viewModelScope。
第4步:与mainactivity一起工作
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.ui_menu, menu)
// Set the item state
lifecycleScope.launch {
val isChecked = viewModel.getUIMode.first()
val item = menu.findItem(R.id.action_night_mode)
item.isChecked = isChecked
setUIMode(item, isChecked)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Handle action bar item clicks here.
return when (item.itemId) {
R.id.action_night_mode -> {
item.isChecked = !item.isChecked
setUIMode(item, item.isChecked)
true
}
else -> super.onOptionsItemSelected(item)
}
}
private fun setUIMode(item: MenuItem, isChecked: Boolean) {
if (isChecked) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
viewModel.saveToDataStore(true)
item.setIcon(R.drawable.ic_night)
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
viewModel.saveToDataStore(false)
item.setIcon(R.drawable.ic_day)
}
}
更新首选项
使用setUIMode() ,如果图标被选中,我们正在更新偏好的状态。我们正在更新图像图标和背景颜色,这样用户界面就会改变。
一旦你完成了,运行该应用程序。
这是你所期望的。

结论
谷歌团队正试图通过推出新的和改进的库,使Android开发每天都变得更容易一些。他们可能会不时地改变或废弃,学习这些工具可以让我们走在时代的前列。