Compose 数据持久化辅助框架:ComposeDataSaver 的一些新变化
七个月前,我写了个用于辅助 Jetpack Compose
做数据持久化的框架,并把它放到了 Github 上。在当时,我还写了篇简单的文章介绍:Jetpack Compose 中优雅完成数据持久化。七个月后,我对它进行了大更新。这篇文章,再来推广推广它。
嘿嘿嘿,不妨看看,说不定有点用呢~
为什么写这个框架
写这个框架是基于这样一个很简单的思想:
我们知道,在Compose中,函数会被反复调用(也就是重组)。所以如果要记住一个状态,需要remember{ }
。也就是这样:
1 | var number by remember{ |
再进一步呢?如果需要页面横竖屏切换时还记住它,我们就需要用到记得更持久一些的rememberSaveable
。也就是这样
1 | var number by rememberSaveable { |
诶,那如果再进一步呢?如果想要它在关闭应用后再打开还是记得住,怎么办?这时候,ComposeDataSaver
就出场啦
1 | // number初始化值为1,之后会自动读取本地已保存数据 |
怎么样,是不是还不错呢?除了上述展示的基本类型,此次更新,我还带来了对自定义类型的更好支持、对List类型的支持以及其他灵活配置的功能。不妨来看看。
它是怎么实现的
框架的原理很简单,整体上,我抽象了数据访问和读取的接口,命名为DataSaverInterface
,它的定义如下:
1 | /** |
使用抽象接口的好处显而易见:我们不限制底层到底是怎么保存和读取的,甚至你也可以选择保存到本地或者直接传到云端。框架本身提供了基于 Preference, DataStore 和 MMKV 的基本实现(后两者为独立的包,以节省体积)。
而为了能让Compose内部能够获取到这个保存的接口,我采取的方案是:CompositionLocal
。如果你不了解,可以参考 官方文档。简单来说,只要根Composable提供了DataSaverInterface
,那么它的所有子Composable都能用。具体就是LocalDataSaver.current
就行 。甚至,如果你闲的慌或者业务需要,你还可以对不同页面使用不同的存储框架(只需要多提供几个就好了)。
接下来就是封装一个State
了。由于mutableStateOf
的实现SnapshotMutableStateImpl
是internal
的,所以没办法直接继承。因此这里采用了组合
的方式,也就是内部维护了一个State
,各种读取操作实际会与这个State
交互,并在值改变时进行持久化。为了使用形式的更统一,我写的这个State
也实现了MutableState
接口,所以你可以把它当做一个普通的MutableState
那样用。
1 | val value by rememberDataSaverState("key_number", 1) |
如果不在Composable里(比如ViewModel
中使用),我们也提供了与mutableState
类似的函数
1 | /** |
上面的代码中出现了两个有趣的参数:savePolicy
和async
,这些都是在此次更新(v1.1.0)中新加入的功能。他们都有默认值,所以你可以无需特别关心;如果你有需要,灵活的配置它们也能满足不同需要。
丢点README的东西过来
控制保存策略
v1.1.0 将原先的 autoSave
升级为了 savePolicy
,以控制是否做、什么时候做数据持久化。mutableDataSaverStateOf
、rememberDataSaverState
均包含此参数,默认为IMEDIATELY
该类目前包含下面三种值:
1 | open class SavePolicy { |
异步保存
v1.1.0 对DataSaverInterface
新增了 suspend fun saveDataAsync
,用于异步保存。默认情况下,它等同于 saveData
。对于支持协程的框架(如DataStore
),使用此实现有助于充分利用协程优势(默认给出的DataStorePreference
就是如此)。
在mutableDataSavarStateOf
和 rememberMutableDataSavarState
函数调用处可以设置async
以启用异步保存,默认为true
。
自定义类型的支持
还记得开始提到,我们这一版加强了对自定义类型的支持。具体来说,库提供了函数registerTypeConverters
来注册自定义类型的save
和restore
方法,之后保存和读取时都会自动做转换。甚至,如果您为 ExampleBean
注册了转换器,那么 List<ExampleBean>
也将自动得到支持(通过 rememberDataSaverListState
)。
一个例子如下:
在使用相应remember
前注册一下
1 | // cause we want to save custom bean, we provide a converter to convert it into String |
然后使用的时候
1 | var beanExample by rememberDataSaverState(KEY_BEAN_EXAMPLE, default = EmptyBean) |
还算简洁?
而且,正如已经提到的,List<ExampleBean>
也同时自动支持:
1 | var listExample by rememberDataSaverListState(key = "key_list_example", default = listOf( |
当然,上面提到的这些已经给出了示例应用:
点击 这里就可以下载啦
写一个库要有库的样子
尽管库不大,但是我仍然秉持着蛮认真的态度完善着它。具体包括:
完整、清晰的
README.md
:FunnySaltyFish/ComposeDataSaver: 在Jetpack Compose中优雅完成数据持久化 | An elegant way to do data persistence in Jetpack Compose丰富的注释:丢两张图吧
Debug信息输出:
-
- 当然这是可以关闭的
/** * 1. DEBUG: 是否输出库的调试信息 * 2. ... */ object DataSaverConfig { var DEBUG = true // 省略 }
欢迎体验
最后嘛,就欢迎体验啦~
顺带,在这段时间,我也在不断完善着自己的开源项目 FunnySaltyFish/FunnyTranslation: 基于Jetpack Compose开发的翻译软件,支持多引擎、插件化~ | Jetpack Compose+MVVM+协程+Room (github.com),最近给它加上了登录注册(基于指纹)、历史记录(Paging3+Room),也适配了 Android 13 的部分特性。它就是使用的此库做的持久化,也欢迎体验。
能来点star就最好啦(笑)