如何在Kotlin中开始使用偏好数据存储

367 阅读5分钟

在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。

数据存储提供了两种实现方式

  1. Preference DataStore - 存储键值对。它与Sharedpreferences相当相似
  2. 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() ,如果图标被选中,我们正在更新偏好的状态。我们正在更新图像图标和背景颜色,这样用户界面就会改变。

一旦你完成了,运行该应用程序。

这是你所期望的。light mode

dark mode

结论

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