ComposeDataSaver
| English Version |
英文的 README 是由译站的长文翻译功能直接一键翻译自中文版本的。这是一个强大的翻译应用程序,利用大型语言模型的力量进行翻译,也由我开发。它还是一个开源的Compose跨平台应用,并使用这个库来保存数据。如果你在寻找一个完整的项目,可以去那看看
优雅地在 Compose Multiplatform ( Android / JVM Desktop / iOS / WASM ) 中完成数据持久化
var booleanExample rememberDataSaverState(, )
booleanExample =
: () {
intExample mutableDataSaverStateOf<>(
dataSaverInterface = AppConfig.dataSaver,
key = ,
initialValue =
)
{
intExample++
}
}
- :tada: 简洁:近似原生 Compose 函数的写法
- :tada: 低耦合:抽象接口,不限制底层保存算法实现
- :tada: 强大:支持基本的数据类型和自定义类型
注:此库是对Compose中使用其他框架(比如 Preference、MMKV、DataStore 等)的封装,不是一个单独的数据保存框架。您可以参考此链接以了解它的设计思想。
在线预览:https://funnysaltyfish.github.io/ComposeDataSaver/
点击下方任意截图即可打开在线预览:
| 预览 1 | 预览 2 | 预览 3 |
|---|
 | |
示例产物下载(始终指向最新正式版 Release):
iOS 产物为未签名的 Simulator 调试包,适合在模拟器或 Xcode 中验证,不能直接作为真机 IPA 安装。
引入
在settings.gradle引入仓库位置
dependencyResolutionManagement {
repositories {
mavenCentral()
}
}
在项目build.gradle引入
dependencies {
implementation "io.github.funnysaltyfish:data-saver-core:1.2.4"
}
注意:自 v1.2.0 起,仓库转为 Compose Multiplatform,发布至 Maven Central,Group Id 也有改变。从 v1.2.0 之前升级版本时请注意更改
示例代码
以下介绍的示例代码均可在 这里 查看具体实现
示例应用采用分组布局,并内置了日志侧边栏,方便直接查看读写日志与状态变化。
配置
项目使用 DataSaverInterface 的实现类来保存数据,因此您需要先提供一个此类对象。
Android
Perference
项目默认包含了使用 Preference 保存数据的实现类 DataSaverPreferences,可如下初始化:
val dataSaverPreferences = DataSaverPreferences(applicationContext)
CompositionLocalProvider(LocalDataSaver provides dataSaverPreferences){
ExampleComposable()
}
除此之外, 我们也提供了基于 MMKV 或者 DataStorePreference 的简单实现
MMKV
- 在上述依赖基础上,额外添加
// if you want to use mmkv
implementation "io.github.funnysaltyfish:data-saver-mmkv:1.2.4"
implementation 'com.tencent:mmkv:1.2.14'
- 如下初始化
MMKV.initialize(applicationContext)
...
val dataSaverMMKV = DefaultDataSaverMMKV
CompositionLocalProvider(LocalDataSaver provides dataSaverMMKV){
}
DataStorePreference
- 在上述依赖基础上,额外添加
// if you want to use DataStore
implementation "io.github.funnysaltyfish:data-saver-data-store-preferences:1.2.4"
def data_store_version = "1.0.0"
implementation "androidx.datastore:datastore:$data_store_version"
implementation "androidx.datastore:datastore-preferences:$data_store_version"
- 如下初始化
val Context.dataStore : DataStore<Preferences> by preferencesDataStore("dataStore")
val dataSaverDataStorePreferences = DataSaverDataStorePreferences(applicationContext.dataStore)
CompositionLocalProvider(LocalDataSaver provides dataSaverDataStorePreferences){
}
JVM Desktop
默认包含了基于 java.util.Properties 的实现类 DataSaverProperties,您可以如下初始化:
val dataSaver = DataSaverProperties("$userHome/$projectName/config.properties")
CompositionLocalProvider(LocalDataSaver provides dataSaver){
ExampleComposable()
}
如果您需要加密存储,可以使用 DataSaverEncryptedProperties 的实现。它基于 AES 算法加密每一项值,您需要提供一个密钥。
val dataSaver = DataSaverEncryptedProperties("$userHome/$projectName/data_saver_encrypted.properties", "FunnySaltyFish")
CompositionLocalProvider(LocalDataSaver provides dataSaver){
ExampleComposable()
}
iOS
iOS 平台默认包含了基于 NSUserDefaults 的实现类 DataSaverNSUserDefaults,它提供了增强的数据类型支持和外部变更监听能力。您可以如下初始化:
val dataSaver = DefaultDataSaverNSUserDefaults
CompositionLocalProvider(LocalDataSaver provides dataSaver){
ExampleComposable()
}
val customDataSaver = DataSaverNSUserDefaults(
userDefaults = NSUserDefaults.standardUserDefaults,
senseExternalDataChange = true
)
iOS 实现支持以下额外特性:
- 丰富的数据类型: 除基本类型外,还支持
ByteArray、List(Int/String/Boolean)、Map(String, Int/String/Boolean)、NSDate、URL 等
- KVO 变更监听: 可监听外部对 NSUserDefaults 的修改
- : 使用 进行内存管理
WASM
WASM 平台基于浏览器的 localStorage 实现数据持久化,适用于 Web 应用。您可以如下初始化:
val dataSaver = DefaultDataSaverLocalStorage
CompositionLocalProvider(LocalDataSaver provides dataSaver){
ExampleComposable()
}
val customDataSaver = DataSaverLocalStorage(
keyPrefix = "MyApp_",
senseExternalDataChange = false
)
WASM 实现特点:
- 轻量级: 基于浏览器原生 localStorage API
- 键前缀: 支持自定义键前缀,避免与其他应用数据冲突
- 基本类型: 支持
String、Int、Long、、、 等基本数据类型
几者默认支持的类型如下所示
保存数据
完成了 CompositionLocalProvider 的赋值后,在其子微件内部可使用 getLocalDataSaverInterface() 获取当前 DataSaverInterface 实例
对于基本数据类型(如String/Int/Boolean)等:
var booleanExample by rememberDataSaverState("KEY_BOOLEAN_EXAMPLE", false)
booleanExample = true
通过赋值,数据即可自动转换、存于本地。就这么简单!
而对于其他数据类型,您需要自己注册类型转换器,告诉框架如何将您的数据转换为字符串,以及如何从字符串还原:
如果您需要存储可空变量,请使用 registerTypeConverters<ExampleBean?>。
请注意,出于代码的实现上的考虑,对于可空类型,设置 state.value = null 或 dataSaverInterface.saveData(key, null) 实际。这意味着,框架的默认实现没有办法正确的保存 “null” 值。当 设置完且下次重新打开应用后,。
如果您需要真的存储 “null” 且 ,请手动处理这部分逻辑。比如,设置一个特殊的值来代表 “null” ,比如 ;如果您有更好的方案,欢迎 PR!
自 v1.2.1 起,您除了使用类型信息来注册转换器,也可以自己写上其他判定条件:
inline fun <reified T> registerTypeConverters(
noinline save: (T) -> String,
noinline restore: (String) -> T,
noinline acceptCondition: (T) -> Boolean
)
当 acceptCondition 为 true 时,框架会调用对应 save 和 restore 方法转换对应数据。
注意:
- registerTypeConverters 请在初始化时调用,确保早于使用
rememberDataSaverState("key", ExampleBean()) 之前
- 多个类型转换器会按照注册顺序反向依次尝试,直到找到合适的转换器。因此,如果您注册了多个相同类型的转换器,框架会使用最后一个符合条件的转换器。
- 您可以通过
DataSaverConverters.typeConverters 获取到注册的全部转换器列表,初始会有默认的一些,如对 String 的支持
在 Composable 函数外使用
有些情况下,您可能需要将 DataSaverState 置于 @Composable 函数外面,比如放在 ViewModel 中。v1.1.0 提供了 mutableDataSavarStateOf 函数用于此用途,该函数将会自动读取并转换已保存的值,并返回 State。
object AppConfig {
val dataSaver = DataSaverMMKV(...)
}
class MyViewModel: ViewModel() {
var username: String by mutableDataSavarStateOf(AppConfig.dataSaver, "username", "")
}
使用其他存储框架
如果默认提供的几种实现无法满足您的需求,您也可以自行继承 DataSaverInterface,并重写 saveData 和 readData 方法分别用于保存数据和读取数据。对于一些支持协程的框架(如DataStore),您也可以重写 saveDataAsync 以实现异步的保存
然后将 LocalDataSaver 提供的对象更改为您自己的类实例
val dataSaverXXX = DataSaverXXX()
CompositionLocalProvider(LocalDataSaver provides dataSaverXXX){
ExampleComposable()
}
后续相同使用即可。
感知外部数据变化
自 v1.1.6 起,框架加入了有限的对外部数据变化感知的支持,具体来说,就是当您在外部修改了某个 key 对应的值时,框架会自动感知到并更新对应的 MutableDataSaverState,从而触发 Composable 的更新。
目前,仅有 rememberDataSaverState 支持此功能,您需要设置 senseExternalDataChange 参数为 true。同时,对应的 DataSaverInterface 也需要设置 senseExternalDataChange 为 true
val dataSaverXXX = DataSaverXXX(senseExternalDataChange = true)
CompositionLocalProvider(LocalDataSaver provides dataSaverXXX){
val stringExample by rememberDataSaverState(
key = key,
initialValue = "Hello World(1)",
senseExternalDataChange = true
)
...
onClick = {
dataSaverXXX.saveData(key, "Hello World(2)")
}
}
其中,MMKV 本身不支持感知数据变化,因此它的数据变化是 DataSaverMMKV 手动提交的。如果你在使用 MMKV 时需要感知数据变化,那么需要调用 DataSaverMMKV::saveData 来做数据保存才可以;Desktop 的基于 Properties 的实现均不支持感知外部数据变化
请注意,当新数据为 null 时,会有以下情况:
- 当使用
rememberDataSaverState 时
- 如果 T 为可空类型,比如 ExampleBean? ,那么正确的设置为 null
- 如果 T 为非空类型,比如 ExampleBean ,那么 State 的 value 会重新变为 initialValue
高级设置
控制保存策略
v1.1.0 将原先的 autoSave 升级为了 savePolicy,以控制是否做、什么时候做数据持久化,该值默认为IMEDIATELY
该类目前包含下面三种值:
open class SavePolicy {
object IMMEDIATELY : SavePolicy()
object DISPOSED: SavePolicy()
object NEVER : SavePolicy()
}
日志设置
目前,库提供了分级日志配置,它们位于 DataSaverConfig 下:
object DataSaverConfig {
var logLevel: DataSaverLogLevel = DataSaverLogLevel.INFO
}
可用级别如下:
enum class DataSaverLogLevel {
NONE,
ERROR,
WARNING,
INFO,
DEBUG,
VERBOSE
}
默认级别为 INFO。在默认配置下,读取、恢复、写入、移除以及外部数据变更等关键日志都可直接看到。
注意:
DataSaverLogger 已移除,如果您之前直接使用它,请改为直接调用 Log
DataSaverConfig.DEBUG 仍保留兼容入口,但后续推荐使用 DataSaverConfig.logLevel
异步保存
v1.1.0 对 DataSaverInterface 新增了 suspend fun saveDataAsync ,用于异步保存。默认情况下,它等同于 saveData。对于支持协程的框架(如DataStore),使用此实现有助于充分利用协程优势(默认给出的DataStorePreference就是如此)。
在mutableDataSavarStateOf的函数调用处可以设置async以启用异步保存,默认为true。
@Preview 支持
项目自 v1.1.6 起支持了 @Preview。具体来说,由于 @Preview 模式下无法正常使用 CompositionLocalProvider,因此额外实现了 DataSaverInMemory,它使用 HashMap 来存储数据,从而不依赖于本地存储以及 CompositionLocalProvider。
@Composable
@ReadOnlyComposable
fun getLocalDataSaverInterface() =
if (LocalInspectionMode.current) DefaultDataSaverInMemory else LocalDataSaver.current
@Preview 模式下,您可能需要重新调用一遍 registerTypeConverter 以重新注册类型转换器。
使用的项目
目前,此库已在下列项目中使用:
如果您正在使用此项目,也欢迎您告知我以补充。
有任何建议或bug报告,欢迎提交issue。PR 就更好啦。