即使rememberUpdatedState没有改变,LaunchedEffect仍在配置更改时执行。

4
如果我正确理解文档的话,如果rememberUpdatedState没有改变,则LaunchedEffect不应再次运行。但是,如果我运行下面这样的代码,它并不能按照预期工作,并且该值在旋转时被更新。
如果没有LaunchedEffect,在配置更改时会记住rememberSaveable,并且输入中的文本是正确的(如果我键入任何内容,输入中仍然有)。这使我相信rememberUpdatedState也不应该改变,但它却被触发了。为什么?
我做错了什么或者这是一个bug?还有其他更好的方法吗?
谢谢 :)
@Composable
fun ThingView(
    thingViewModel: ThingViewModel,
    id: String?
) {
    var thingName by rememberSaveable { mutableStateOf("") }
    val scope = rememberCoroutineScope()
    
    LaunchedEffect(rememberUpdatedState(newValue = thingName)) {
        scope.launch {
            id?.let {
                val thing = thingViewModel.getThing(id)
                thingName = thing.name
            }
        }
    }
    
    OutlinedTextField(
        value = thingName,
        onValueChange = { thingName = it },
        label = { Text("Name") }
    )
}

编辑:

澄清一下,目标是允许用户在文本框中输入文本并在旋转时不清除该文本。对于用户来说,这将非常令人恼火,可能不符合他们的期望。


你能找到任何解决方案吗? - Abhinav
很遗憾,目前还没有。自从我发布后,我并没有真正关注它,但最终我会回来看看的。 - Michael Vescovo
1个回答

3
我认为你看到的是在配置更改时旋转,LaunchedEffect状态被清除。因此,我认为没有任何值可以作为键提供以防止其运行。

有两个选项可供选择。按照原始代码的风格,您可以考虑仅在确定状态已更改时声明LaunchedEffect。例如,您可能会在可保存的lastId中保留上次查看的id,并在输入时测试更改。当然,这需要您最初具有lastId的哨兵值。

@Composable
fun ThingView(
    thingViewModel: ThingViewModel,
    id: String?
) {
    var thingName by rememberSaveable { mutableStateOf("") }
    var lastId: String? by rememberSaveable { mutableStateOf(null) }
    val scope = rememberCoroutineScope()

    if (lastId != id) {
        lastId = id
        LaunchedEffect(id) {
            scope.launch {
                id?.let {
                    val thing = thingViewModel.getThing(id)
                    thingName = thing.name
                }
            }
        }
    }
    
    OutlinedTextField(
        value = thingName,
        onValueChange = { thingName = it },
        label = { Text("Name") }
    )
}

另一种可能更加传统的选择是使用 ViewModel 在配置更改时保持状态。 您可以在每个 onValueChange 上更新 ViewModel,并要求 ViewModel 将当前值返回给 OutlinedTextField。 在此样式中,您可能不需要效果或协程。

关于我对 rememberUpdatedState 的理解,只想说一句话。 我认为它通常旨在允许具有长期生存期的协同程序或回调引用组合范围内变量的当前值,而无需在每次状态更改时重新创建协同程序或回调。 闭包似乎仅限于该效果,并且在创建时捕获值。 我还在努力更好地理解它。


嘿,我刚刚添加了一个“编辑”以澄清我的意图。它只是为了防止textField中的文本在旋转时被清除。你能让它工作吗?实际上,我正在使用LaunchedEffect中的id。那只是一些测试代码,以找出为什么LaunchedEffect在旋转时被运行。该id没有改变。 - Michael Vescovo
@MichaelVescovo 我调整了回复,提供了几个选项以避免在旋转时重新初始化文本。 - user650881
如果我像你在lastId和rememberSavable中那样在本地声明id,那就可以工作。似乎无法从构造函数传递值到LaunchedEffect。我现在也感到困惑了,记得更新状态和这是很久以前的事情,我在想我是否还记得我当时试图做什么。不管怎样,我认为这暂时解决了我的问题。谢谢 :) - Michael Vescovo
很高兴听到它起作用了。请不要得出构造函数值不能在LaunchedEffect中使用的结论。一个可以将构造函数值传递到LaunchedEffect中。 - user650881
我认为这里的教训首先是,我不记得我当时在尝试使用rememberUpdatedState做什么,因为那是很久以前的事了!我想它是为了文本保留在那里,所以我们就用这个吧。其次,对于这种用例,您不需要它。此外,如果您将构造函数值传递到LaunchedEffect中,即使该值不更改,LaunchedEffect也会在旋转时重新运行。如果这不是您想要的,请复制该值并将其存储在rememberSavable中。 - Michael Vescovo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接