为什么在回调中调用ViewModel时会发生重组?

问题描述 投票:0回答:3

我完全混淆了撰写概念。 我有代码

@Composable
fun HomeScreen(viewModel: HomeViewModel = getViewModel()) {
    Scaffold {
        val isTimeEnable by viewModel.isTimerEnable.observeAsState()
        Column(
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center,
            modifier = Modifier
                .fillMaxSize()
                .background(Color.Black),
        ) {
            Switch(
                checked = isTimeEnable ?: false,
                onCheckedChange = {
                    viewModel.setTimerEnable(it)
                },
            )
            Clock(viewModel.timeSelected.value!!) {
                viewModel.setTime(it)
            }
        }
    }
}

@Composable
fun Clock(date: Long, selectTime: (date: Date) -> Unit) {
    NumberClock(Date(date)) {
        val time = SimpleDateFormat("HH:mm", Locale.ROOT).format(it)
        Timber.d("Selected time: time")
        selectTime(it)
    }
}

为什么当我点击开关时

Clock
小部件会重新组合。如果我从
selectTime(it)
中删除行
Clock
,则不会发生小部件回调重组。 撰写版本:1.0.2

android android-jetpack-compose viewmodel android-viewmodel compose-recomposition
3个回答
2
投票

这是因为就组合而言,您每次都在创建一个新的

selectTime
lambda,因此重新组合是必要的。如果你传递
setTime
函数作为引用,compose 就会知道它是同一个函数,因此不需要重新组合:

Clock(viewModel.timeSelected.value!!, viewModel::setTime)

或者,如果您有更复杂的处理程序,您可以

remember
它。双括号 (
{{ }}
) 在这里很重要,因为你需要记住 lambda。

Clock(
    date = viewModel.timeSelected.value!!,
    selectTime = remember(viewModel) {
        {
            viewModel.setTimerEnable(it)
        }
    }
)

我知道它看起来有点奇怪,你可以使用

rememberLambda
这将使你的代码更具可读性:

selectTime = rememberLambda(viewModel) {
    viewModel.setTimerEnable(it)
}

请注意,您需要将所有可能更改的值作为键传递,因此

remember
将根据需要重新计算。


总的来说,重组并不是一件坏事。当然,如果您可以减少它,您应该这样做,但是即使您的代码被重组很多次,它也应该可以正常工作。例如,您不应该在可组合项内部进行大量计算来执行此操作,而应使用副作用

因此,如果重新组合

Clock
导致奇怪的 UI 效果,则您的
NumberClock
可能存在问题,无法在重新组合后继续存在。如果是这样,请将
NumberClock
代码添加到您的问题中,以获取有关如何改进它的建议。


0
投票

资源

TLDR(太长没读)

  • 编写 lambda 时,编译器将使用该代码创建一个匿名类。如果 lambda 需要访问外部变量(即 ViewModel),编译器会将这些变量添加为传递到 lambda 构造函数的字段。 ViewModel 违反了 @Stable 的要求,即所有公共属性也必须是 @Stable

记住修复


val updateClickedUser:(String,String,Boolean,Boolean)->Unit = remember(streamViewModel) { { username, userId, banned, isMod ->
        streamViewModel.updateClickedChat(
                username,
                userId,
                banned,
                isMod
            )
    } }
    ChatUI(
        twitchUserChat = twitchUserChat,
        updateClickedUser = { username, userId, banned, isMod ->
            updateClickedUser(
                username,
                userId,
                banned,
                isMod
            )

        },
    )


-1
投票

这是预期的行为。当用户切换开关时(通过调用 vm.setTimeenabled),您显然正在修改视图模型内的

isTimeEnabled
字段。现在,很明显,视图模型中的
isTimeEnabled
是一个
LiveData
实例,并且您通过在可组合对象中调用
observeAsState
来引用该实例。因此,当您修改开关的 onValueChange 中的值时,您实际上是在修改可组合项所依赖的状态。因此,为了呈现更新的状态,会触发重组

© www.soinside.com 2019 - 2024. All rights reserved.