在 Jetpack Compose 中轻松使用持久化(使用mmkv持久化状态)

607 阅读2分钟

在实际开发中,我们经常会遇到一些字段在应用中需要反复使用。

过去最常见的做法就是在 Application 中申明一个属性,使用它来暂存这个字段,这是非常常见的内存持久化方案,这些暂存内容会在应用退出后丢失,属于短期持久化

另一个场景就是使用 MMKV 这样的键值对持久化工具,将需要持久化的内容保存到本地文件,这些内容退出后不会丢失,再次打开应用时会重新读取加载,属于长期持久化

在 Compose 中需要我们可以使用 junerver/ComposeHooks 中的 usePersistent 来轻松做到这一点。

默认内存持久化-简单的全局状态

@Composable  
private fun DefaultPersistent() {  
    var count by usePersistent(key = "count", -1)  
    Column {  
        Text(text = "DefaultPersistent : exit app will lose state")  
        TButton(text = "+1") {  
            count += 1  // 如同使用状态一样,直接进行赋值写操作
        }  
        SubShowCount()  
    }  
}  
  
@Composable  
private fun SubShowCount() {  
    val count by usePersistent(key = "count", -1)  //子组件使用同一个key
    Text(text = "persistent: $count")  // 父组件数值改变时子组件一样跟随改变
}

它开箱即用,你只需要调用 usePersistent(key = "count", -1),传入一个 key 与默认值即可,默认使用内存进行持久化保存。

在应用全局同一个 key 对应同一个对象,一处修改、处处生效。

自定义持久化-使用mmkv

除了内存持久化,usePersistent 同样支持自定义持久化方案,例如使用 mmkv:

val mmkv = MMKV.defaultMMKV()  
  
fun mmkvSave(key: String, value: Any?) {  
    when (value) {  
        is Int -> mmkv.encode(key, value)  
        is Long -> mmkv.encode(key, value)  
        is Double -> mmkv.encode(key, value)  
        is Float -> mmkv.encode(key, value)  
        is Boolean -> mmkv.encode(key, value)  
        is String -> mmkv.encode(key, value)  
        is ByteArray -> mmkv.encode(key, value)  
        is Parcelable -> mmkv.encode(key, value)  
    }    notifyDefaultPersistentObserver(key) //必须要调用该函数触发状态修改 
}  
  
fun mmkvGet(key: String, value: Any): Any {  
    return when (value) {  
        is Int -> mmkv.decodeInt(key, value)  
        is Long -> mmkv.decodeLong(key, value)  
        is Double -> mmkv.decodeDouble(key, value)  
        is Float -> mmkv.decodeFloat(key, value)  
        is Boolean -> mmkv.decodeBool(key, value)  
        is String -> mmkv.decodeString(key, value)  
        is ByteArray -> mmkv.decodeBytes(key, value)  
        is Parcelable -> mmkv.decodeParcelable(key, value.javaClass)  
        else -> error("wrong type of default value!")  
    } as Any  
}  
  
fun mmkvClear(key: String) {  
    mmkv.remove(key)  
    notifyDefaultPersistentObserver(key) //必须要调用该函数触发状态修改
}


PersistentContext.Provider( // 使用该Provider提供一个三元组
    value = Triple(
        first = ::mmkvGet,
        second = ::mmkvSave,
        third = ::mmkvClear
    )
) {
    val (hideKeyboard) = useKeyboard()
    var token by usePersistent(key = "token", "") // 此时对状态的读写持久化到mmkv
    val (state, setState) = useGetState("")
    Column {
        Text(text = "MMKVPersistent : exit app will NOT lose state")
        Text(text = "token: $token")
        OutlinedTextField(value = state.value, onValueChange = setState)
        TButton(text = "saveToken") {
            hideKeyboard()
            token = state.value
            setState("")
            println("now you can exit app,and reopen")
        }
        MMKVPersistentSub()
    }
}

自定义持久化时需要使用 PresistentContext.Provider 这个容器组件,向这个组件提供一个三元组 Triple<PersistentGet, PersistentSave, PersistentClear>

这个三元组接收三个函数,分别对应了持久化获取、保存、移除这三个操作,现在处于 PresistentContext.Provider 这个容器组件下的 usePersistent 函数将会使用自定义的 mmkv 进行持久化操作。

自定义存储与自定义的移除操作时请务必调用 notifyDefaultPersistentObserver(key) 这个函数来通知 hook 状态变更了

清除持久化内容

除了读写,在有的场景下,我们需要移除持久化内容,这种场景下就不能继续使用 by 关键字进行委托了,需要直接使用 =

usePersistent 函数的返回值是一个 data class ,我们可以使用解构声明轻松的获取其成员:

@Stable
data class PersistentHolder<T>(
    val state: State<T>,
    val save: SaveToPersistent<T>,
    val clear: HookClear,
) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T = state.value

    operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) {
        save(newValue)
    }
}

我们需要进行如下操作,修改 by=,通过解构声明获取第三个元素的值就是 clear 函数

@Composable
private fun SubShowCount() {
    val (countState,_,clear) = usePersistent(key = "count", -1)
    Text(text = "persistent: ${countState.value} ,click to clear",modifier = Modifier.clickable { clear() })
}

这时我们调用 clear 函数,将会移除这个key对应的存储内容,并将状态修改为默认值

在自定义存储容器组件下强制使用内存持久化

在使用了自定义持久化的情况下,PresistentContext.Provider 这个容器组件下所有的 usePresistent 函数将默认使用自定义持久化方案,可以通过配置参数 forceUseMemory = true 来强制使用内存持久化:

@Composable
private fun MMKVPersistentSub() {
    val (token, _, clear) = usePersistent(key = "token", "321")
    var clearCount by usePersistent(key = "clearCount", 0, forceUseMemory = true) // 这个key对应的状态将强制使用内存进行持久化
    Text(text = "sub component token: ${token.value} ,click to clear", modifier = Modifier.clickable {
        clear()
        clearCount += 1
    })
    Text("current clear count: $clearCount")
}

探索更多

好了以上就是 使用 hooks 的一些小小技巧,现在你可以使用 usePresistent 来轻松的使用全局状态、持久化状态!

示例源码地址:UsePersistentExample

项目开源地址:junerver/ComposeHooks

MavenCentral:hooks2

本项目已经迁移到 Compose Multiplatform ,使用新的工件 id:hooks2

如果你在 CMP 依赖,直接使用:

implementation("xyz.junerver.compose:hooks2:2.1.0-alpha0")

如果你在 Android 环境依赖,请使用 id:hooks2-android

implementation("xyz.junerver.compose:hooks2-android:2.1.0-alpha0")

详细迁移说明请查看wiki

欢迎使用、勘误、pr、star。