/ComposeDataSaver

在Jetpack Compose中优雅完成数据持久化 | An elegant way to do data persistence in Jetpack Compose

Primary LanguageKotlinApache License 2.0Apache-2.0

ComposeDataSaver

Version License

| English Version |

优雅地在Jetpack Compose中完成数据持久化

// booleanExample 初始化值为false
// 之后会自动读取本地数据
var booleanExample by rememberDataSaverState(KEY_BOOLEAN_EXAMPLE, false)
// 直接赋值即可完成持久化
booleanExample = true
  • 🎉 简洁:近似原生Compose的写法
  • 🎉 低耦合:抽象接口,不限制底层保存算法实现
  • 🎉 轻巧:默认不引入除Compose外任何第三方库(主体jar包约10kb,可选的实现仅1kb
  • 🎉 强大:支持基本的数据类型和自定义类型、支持List类型

注:此库是对Compose中使用其他框架(比如Preference、MMKV、DataStore)的封装,不是一个单独的数据保存框架。您可以参考此链接以了解它的设计**。

Example

您可以点击 这里下载demo体验


引入

settings.gradle引入jitpack仓库位置

dependencyResolutionManagement {
    repositories {
        maven { url "https://jitpack.io" }
    }
}

在项目build.gradle引入

dependencies {
    implementation "com.github.FunnySaltyFish.ComposeDataSaver:data-saver:{tag}"
}

基本使用

项目使用DataSaverInterface接口的实现类来保存数据,因此您需要先提供一个此类对象。

项目默认包含了使用Preference保存数据的实现类DataSaverPreferences,可如下初始化:

// init preferences
val dataSaverPreferences = DataSaverPreferences(applicationContext)
CompositionLocalProvider(LocalDataSaver provides dataSaverPreferences){
	ExampleComposable()
}

此后在ExampleComposable及其子微件内部可使用LocalDataSaver.current获取当前实例

对于基本数据类型(如String/Int/Boolean):

// booleanExample 初始化值为false
// 之后会自动读取本地数据
var booleanExample by rememberDataSaverState(KEY_BOOLEAN_EXAMPLE, false)
// 直接赋值即可完成持久化
booleanExample = true

v1.1.0 起新增了对列表的支持,使用方式为:

var listExample by rememberDataSaverListState(key = "key_list_example", default = listOf(...))
// 删除一个
onClick = { listExample = listExample.dropLast(1) }

通过赋值,数据即可自动转换、存于本地。就这么简单!

在Composable函数外使用

有些情况下,您可能需要将 DataSaverState 置于 @Composable 函数外面,比如放在 ViewModel 中。v1.1.0提供了 mutableDataSavarStateOf 函数用于此用途,该函数将会自动读取并转换已保存的值,并返回State。

此函数签名如下:

/**
 * This function READ AND CONVERT the saved data and return a [DataSaverMutableState].
 * Check the example in `README.md` to see how to use it.
 *
 * 此函数 **读取并转换** 已保存的数据,返回 [DataSaverMutableState]
 *
 * @param key String 键
 * @param initialValue T 如果本地还没保存过值,此值将作为初始值;其他情况下会读取已保存值
 * @param savePolicy 管理是否。何时做持久化操作,见 [SavePolicy]
 * @param async 是否异步做持久化
 * @return DataSaverMutableState<T>
 *
 * @see DataSaverMutableState
 */
inline fun <reified T> mutableDataSaverStateOf(
    dataSaverInterface: DataSaverInterface,
    key: String,
    initialValue: T,
    savePolicy: SavePolicy = SavePolicy.IMMEDIATELY,
    async: Boolean = true
): DataSaverMutableState<T>

自定义存储框架

我们提供了基于 MMKV 或者 DataStorePreference 的简单实现

MMKV

  1. 在上述依赖基础上,额外添加
// if you want to use mmkv
implementation "com.github.FunnySaltyFish.ComposeDataSaver:data-saver-mmkv:{tag}"
implementation 'com.tencent:mmkv:1.2.14'
  1. 如下初始化
MMKV.initialize(applicationContext)
...

val dataSaverMMKV = DefaultDataSaverMMKV
// DefaultDataSaverMMKV 是我们提供的默认实现,您可以在任何地方使用它,就像一个 MMKVUtils 那样
// 如果有定制 MMKV 的需要,可以选择 DataSaverMMKV(MMKV.defaultMMKV())

CompositionLocalProvider(LocalDataSaver provides dataSaverMMKV){
    // ...
}

DataStorePreference

  1. 在上述依赖基础上,额外添加
// if you want to use DataStore
implementation "com.github.FunnySaltyFish.ComposeDataSaver:data-saver-data-store-preferences:{tag}"
def data_store_version = "1.0.0"
implementation "androidx.datastore:datastore:$data_store_version"
implementation "androidx.datastore:datastore-preferences:$data_store_version"
  1. 如下初始化
val Context.dataStore : DataStore<Preferences> by preferencesDataStore("dataStore")
val dataSaverDataStorePreferences = DataSaverDataStorePreferences(applicationContext.dataStore)

CompositionLocalProvider(LocalDataSaver provides dataSaverDataStorePreferences){
    // ...
}

三者默认支持的类型如下所示

类型 DataSaverPreference DataSaverMMKV DataSaverDataStorePreferences
Int Y Y Y
Boolean Y Y Y
String Y Y Y
Long Y Y Y
Float Y Y Y
Double Y Y
Parceable Y
ByteArray Y

更多类型的支持请参见 保存自定义类型


使用其他存储框架

只需要实现DataSaverInterface类,并重写saveDatareadData方法分别用于保存数据和读取数据。对于一些支持协程的框架(如DataStore),您也可以重写saveDataAsync以实现异步的保存

interface DataSaverInterface{
    fun <T> saveData(key:String, data : T)
    fun <T> readData(key: String, default : T) : T
    suspend fun <T> saveDataAsync(key:String, data : T) = saveData(key, data)
    fun remove(key: String)
}

然后将LocalDataSaver提供的对象更改为您自己的类实例

val dataSaverXXX = DataSaverXXX()
CompositionLocalProvider(LocalDataSaver provides dataSaverXXX){
    ExampleComposable()
}

后续相同使用即可。

保存自定义类型

1.1.0起,对自定义类型提供了更完善的支持。

因为默认的DataSaverPreferences并不提供自定义类型的保存(当尝试这样做时会报错),所以您可以从以下两种方式中任选其一以保存自定义数据。

  1. 通过DataSaverConverter.registerTypeConverters将实体类序列化为 String 再储存
  2. 重写自己的DataSaverInterface实现类(见上)并实现相关的保存方法

对于第一种方式,您需要为对应实体类添加转换器,以实现保存时自动转换为String、并从String还原。方法如下:

@Serializable
data class ExampleBean(var id:Int, val label:String)
// ------------ //

// 在初始化时调用registerTypeConverters方法注册对应转换方法
// 该方法接收两个参数:分别用于 转成可序列化类型以保存 和 反序列化为您的Bean
// 此处使用 Json.encodeToString 和 Json.decodeFromString, 您也可以用 Gson、Fastjson 等
registerTypeConverters<ExampleBean>(
	save = { bean -> Json.encodeToString(bean) },
	restore = { str -> Json.decodeFromString(str) }
)

通过注册类型转换器,框架即可在remember和save时自动尝试转换。甚至,如果您为 ExampleBean 注册了转换器,那么 List<ExampleBean> 也将自动得到支持(通过 rememberDataSaverListState

完整例子见 示例项目

高级设置

控制保存策略

v1.1.0 将原先的 autoSave 升级为了 savePolicy,以控制是否做、什么时候做数据持久化。mutableDataSaverStateOfrememberDataSaverState 均包含此参数,默认为IMEDIATELY

该类目前包含下面三种值:

open class SavePolicy {
    /**
     * 默认模式,每次给state的value赋新值时就做持久化
     */
    object IMMEDIATELY : SavePolicy()

    /**
     * Composable `onDispose` 时做数据持久化,适合数据变动比较频繁、且此Composable会进入onDispose的情况。
     * **慎用此模式,因为有些情况下onDispose不会被回调**
     */
    object DISPOSED: SavePolicy()

    /**
     * 不会自动做持久化操作,请按需自行调用`state.saveData()`。
     * Example: `onClick = { state.saveData() }`
     */
    object NEVER : SavePolicy()
}

设置库参数

目前,库提供了一些可以设置的参数,它们位于DataSaverConfig

/**
 * 1. DEBUG: 是否输出库的调试信息
 * 2. LIST_SEPARATOR: 内置的 列表转字符串 使用的分隔符,默认为'#@#'。(**请不要使用 ',',因为单个bean 序列化后的json中会包含它** )
 */
object DataSaverConfig {
    var DEBUG = true
    var LIST_SEPARATOR = "#@#"
}

异步保存

v1.1.0 对DataSaverInterface 新增了 suspend fun saveDataAsync ,用于异步保存。默认情况下,它等同于 saveData。对于支持协程的框架(如DataStore),使用此实现有助于充分利用协程优势(默认给出的DataStorePreference就是如此)。

mutableDataSavarStateOfrememberMutableDataSavarState 函数调用处可以设置async以启用异步保存,默认为true

Null 支持

v1.1.4 放宽了自定义类型时为 null 的情况,可以通过

registerTypeConverters<ExampleBean?>(
    save = { bean -> Json.encodeToString(bean) },
    restore = { str -> Json.decodeFromString(str) }
)

来支持 null 作默认值

val nullableCustomBeanState: DataSaverMutableState<ExampleBean?> = rememberDataSaverState(key = "nullable_bean", initialValue = null)

请注意,出于代码的实现上的考虑,设置 state.value = nulldataSaverInterface.saveData(key, null) 实际将调用对应 remove 方法直接移除对应值

使用的项目

目前,此库已在下列项目中使用:

如果您正在使用此项目,也欢迎您告知我以补充。

有任何建议或bug报告,欢迎提交issue。PR就更好啦。