【问题标题】:Jetpack Compose saving state on orientation changeJetpack Compose 在方向更改时保存状态
【发布时间】:2020-09-04 02:19:05
【问题描述】:

我正在使用 Android Jetpack 的 Compose,并且一直在尝试弄清楚如何保存状态以更改方向。

我的思路是让一个类成为 ViewModel。当我使用 Android 的传统 API 时,这通常是有效的。

我使用 remember {} 和 mutableState {} 在信息发生更改时更新 UI。 请验证我的理解是否正确...

remember = 保存变量并允许通过 .value 访问,这允许缓存值。但它的主要用途是在更改时不重新分配变量。

mutableState = 在发生更改时更新变量。

许多博客文章都说要使用@Model,但是在尝试该方法时导入会出错。 所以,我加了一个:ViewModel()

但是,我相信我的记忆 {} 正在阻止它按预期工作?

我能指出正确的方向吗?

@Composable
fun DefaultFlashCard() {

    val flashCards = remember { mutableStateOf(FlashCards())}
    

    Spacer(modifier = Modifier.height(30.dp))

    MaterialTheme {


        val typography = MaterialTheme.typography
        var question = remember { mutableStateOf(flashCards.value.currentFlashCards.question) }



        Column(modifier = Modifier.padding(30.dp).then(Modifier.fillMaxWidth())
                .then(Modifier.wrapContentSize(Alignment.Center))
                .clip(shape = RoundedCornerShape(16.dp))) {
            Box(modifier = Modifier.preferredSize(350.dp)
                    .border(width = 4.dp,
                            color = Gray,
                            shape = RoundedCornerShape(16.dp))
                    .clickable(
                            onClick = {
                                question.value = flashCards.value.currentFlashCards.answer })
                    .gravity(align = Alignment.CenterHorizontally),
                    shape = RoundedCornerShape(2.dp),
                    backgroundColor = DarkGray,
                    gravity = Alignment.Center) {
                Text("${question.value}",
                        style = typography.h4, textAlign = TextAlign.Center, color = White
                )
            }
        }

        Column(modifier = Modifier.padding(16.dp),
                horizontalGravity = Alignment.CenterHorizontally) {

            Text("Flash Card application",
                    style = typography.h6,
                    color = Black)

            Text("The following is a demonstration of using " +
                    "Android Compose to create a Flash Card",
                    style = typography.body2,
                    color = Black,
                    textAlign = TextAlign.Center)

            Spacer(modifier = Modifier.height(30.dp))
            Button(onClick = {
                flashCards.value.incrementQuestion();
                question.value = flashCards.value.currentFlashCards.question },
                    shape = RoundedCornerShape(10.dp),
                    content = { Text("Next Card") },
                    backgroundColor = Cyan)
        }
    }
}


data class Question(val question: String, val answer: String) {
}


class FlashCards: ViewModel() {

    var flashCards = mutableStateOf( listOf(
            Question("How many Bananas should go in a Smoothie?", "3 Bananas"),
            Question("How many Eggs does it take to make an Omellete?", "8 Eggs"),
            Question("How do you say Hello in Japenese?", "Konichiwa"),
            Question("What is Korea's currency?", "Won")
    ))

    var currentQuestion = 0

    val currentFlashCards
        get() = flashCards.value[currentQuestion]

    fun incrementQuestion() {
        if (currentQuestion + 1 >= flashCards.value.size) currentQuestion = 0 else currentQuestion++
    }
}

【问题讨论】:

    标签: android kotlin android-jetpack android-viewmodel android-jetpack-compose


    【解决方案1】:

    在 Compose 中还有另一种处理配置更改的方法,它是 rememberSaveable。作为docs says:

    虽然remember 可帮助您在重新组合时保留状态,但在配置更改时不会保留该状态。为此,您必须使用rememberSaveable。 rememberSaveable 自动保存任何可以保存在 Bundle 中的值。对于其他值,您可以传入自定义保护程序对象。

    似乎Mohammad's solution更健壮,但这个似乎更简单。

    【讨论】:

    • 这种方法的问题在于 Bundle 施加了 2 个限制:要存储的对象必须是可序列化的,并且不能很大,否则我们会得到 TransactionTooLarge 异常。我想在 Composable 的生命周期(比 ViewModel 的生命周期短)中将一个对象存储在内存中,该对象在配置更改后仍然存在。
    【解决方案2】:

    更新:

    在 Compose 中有 2 种内置方式可以持久化状态:

    1. remember:用于在重新组合之间的可组合函数中保存状态。

    2. rememberSaveable: remember 仅在重新组合之间保存状态并且不处理配置更改和进程死亡,所以为了避免配置更改和进程死亡,您应该改用remeberSaveable。

    但是rememberSaveable也有一些问题:

    1. 支持开箱即用的原始类型,但对于更复杂的数据,例如data class,您必须创建一个Saver 来解释如何将状态持久化到包中,

    2. rememberSaveable 在后台使用Bundle,所以你可以在其中保存多少数据是有限制的,如果数据太大你将面临TransactionTooLarge 异常。

    如上所述,以下解决方案可用:

    1. 设置android:configChangesin Manifest 以避免在配置更改中重新创建活动。 (在进程死亡中没有用,也不会让您在 Android 12 的壁纸更改中重新创建)

    2. 结合使用ViewModel + remeberSaveable + 存储中的数据持久性

    ================================================ ========

    旧答案

    和以前一样,您可以使用架构组件 ViewModel 来保留配置更改。

    您应该在 Activity/Fragment 中初始化您的 ViewModel,然后将其传递给 Composable 函数。

    class UserDetailFragment : Fragment() {
    
        private val viewModel: UserDetailViewModel by viewModels()
    
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
    
            return ComposeView(context = requireContext()).apply {
                setContent {
                    AppTheme {
                        UserDetailScreen(
                            viewModel = viewModel
                        )
                    }
                }
            }
        }
    }
    

    那么您的 ViewModel 应该通过 LiveData 或 Flow 之类的方式公开 ViewState

    UserDetailViewModel:

    class UserDetailViewModel : ViewModel() {
        private val _userData = MutableLiveData<UserDetailViewState>()
        val userData: LiveData<UserDetailViewState> = _userData
    
    
        // or
    
        private val _state = MutableStateFlow<UserDetailViewState>()
        val state: StateFlow<UserDetailViewState>
            get() = _state
    
    }
    

    现在您可以在可组合函数中观察到这种状态:

    @Composable
    fun UserDetailScreen(
        viewModel:UserDetailViewModel
    ) {
        val state by viewModel.userData.observeAsState()
        // or
        val viewState by viewModel.state.collectAsState()
    
    }
    

    【讨论】:

    • 这绝对是在配置更改后保留状态的首选方法。如果可以,请标记为正确! : x
    • 这种方法的问题在于 ViewModel 位于 Activity/Fragment 级别,而remember 是 Composable 的本地。如果我们有可组合的 UI 元素在同一个 Activity 中来来去去,我希望状态与可组合的生命周期一起读取和处置,而不是与 Activity 的较长生命周期一起读取和处理。加上幸存的配置更改
    猜你喜欢
    • 1970-01-01
    • 2015-11-23
    • 1970-01-01
    • 2022-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多