【Compose 错误使用rememberSaveable导致java.lang.IllegalArgumentException: Failed requirement问题解决记录】

Compose 错误使用rememberSaveable导致java.lang.IllegalArgumentException: Failed requirement问题解决记录

先看报错信息
报错

异常堆栈:

E  FATAL EXCEPTION: main
Process: com.lie.composeanimationkit, PID: 8609
java.lang.IllegalArgumentException: Failed requirement.
at androidx.compose.runtime.saveable.ListSaverKt$listSaver$1.invoke(ListSaver.kt:39)
at androidx.compose.runtime.saveable.ListSaverKt$listSaver$1.invoke(ListSaver.kt:33)
at androidx.compose.runtime.saveable.SaverKt$Saver$1.save(Saver.kt:66)
at androidx.compose.runtime.saveable.RememberSaveableKt$rememberSaveable$1$valueProvider$1.invoke(RememberSaveable.kt:100)
at androidx.compose.runtime.saveable.RememberSaveableKt$rememberSaveable$1.invoke(RememberSaveable.kt:102)
at androidx.compose.runtime.saveable.RememberSaveableKt$rememberSaveable$1.invoke(RememberSaveable.kt:98)
at androidx.compose.runtime.DisposableEffectImpl.onRemembered(Effects.kt:81)
at androidx.compose.runtime.CompositionImpl$RememberEventDispatcher.dispatchRememberObservers(Composition.kt:802)
at ...

定位到代码处:

@Composable
fun rememberLoadingButtonState(
    initialState: LoadingState = LoadingState.Ready,
): LoadingButtonState = rememberSaveable(saver = LoadingButtonState.Saver) {
    LoadingButtonState(initialState)
}

这里我定义了一个rememberLoadingButtonState方法,调用rememberSaveable来记录按钮的状态,

LoadingState是我定义的一个密封类,对应各种状态

sealed class LoadingState {
    object Ready : LoadingState()
    object Loading : LoadingState()
    object Success : LoadingState()
    object Error : LoadingState()
}

LoadingButtonState是状态的持有者,同时内部实现了Saver 来存储和读取状态

class LoadingButtonState(val currentState: LoadingState = LoadingState.Ready) {

    companion object {
        /**
         * The default [Saver] implementation for [PagerState].
         */
        val Saver: Saver<LoadingButtonState, *> = listSaver(
            save = {
                listOf<Any>(
                    it.currentState,
                )
            },
            restore = {
                LoadingButtonState(
                    currentState = it[0] as LoadingState,
                )
            }
        )
    }
}

问题原因:

定位到Saver类,看Saver的说明信息:

The Saver describes how the object of Original class can be simplified and converted into something which is Saveable.
What types can be saved is defined by SaveableStateRegistry, by default everything which can be stored in the Bundle class can be saved. The implementations can check that the provided value can be saved via SaverScope.canBeSaved
You can pass the implementations of this class as a parameter for rememberSaveable.

因为rememberSaveable实际是基于onSaveInstanceState实现的,具体可以看fundroid大佬的这篇文章:
Compose状态保存rememberSaveable原理解析

因此同样,Saver存储和读取的数据必须是能写进Bundle的基本数据类型或者Parcle,具体可以用SaverScope.canBeSaved()方法判断是否支持存储

了解问题原因之后就好办了,上面的代码中,Saver存储的是自定义的密封类对象,所以不支持Saver.save方法,那我们就改一下:

sealed class LoadingState(val code: Int) {
    object Ready : LoadingState(0)
    object Loading : LoadingState(1)
    object Success : LoadingState(2)
    object Error : LoadingState(3)

    companion object {
        fun getState(code: Int): LoadingState {
            return when (code) {
                0 -> Ready
                1 -> Loading
                2 -> Success
                else -> Error
            }
        }
    }
}

为了避免太大的改动,继续保留密封类的正常使用,可以参考这么去改LoadingState密封类:

  1. 密封类的构造方法中添加一个基本数据类型参数code,注意前面要有val,不然不是类的成员属性,而只单纯作为构造参数的话,是无法读取的
  2. 密封类添加静态方法,根据code返回对应的密封类对象

思路其实类似与emun枚举类的getValue()方法

然后Saver处也要调整一下存储和写入的规则:

class LoadingButtonState(val currentState: LoadingState = LoadingState.Ready) {

    companion object {
        /**
         * The default [Saver] implementation for [PagerState].
         */
        val Saver: Saver<LoadingButtonState, *> = listSaver(
            save = {
                listOf<Any>(
                    it.currentState.code,	//改为取基本数据类型code,存储
                )
            },
            restore = {
                LoadingButtonState(
                    currentState = LoadingState.getState(it[0] as Int),	//根据code获取对应的密封类对象
                )
            }
        )
    }
}

然后其他地方都不需要调整,密封类对象和Saver的使用方式还是和之前的一样

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值