如何绑定 ViewModel 生命周期来组合
how to bind ViewModel life cycle to compose
我现在正在使用 Jetpack Compose。
我意识到我可以按照我的可组合和初始化视图模型在可组合中使用 ViewModel,就像这样:
val myViewModel:MyViewModel = viewModel()
但存在一个问题,即这些视图模型永远不会被销毁,即使未显示可组合项也是如此。
例如,我有一个主可组合屏幕,它根据用户交互加载一些其他屏幕,如下所示:
@Composable
fun MainAuthentication(viewModel: MainViewModel) {
val state = viewModel.state.value
val scope = rememberCoroutineScope()
val scaffoldState = rememberScaffoldState()
Scaffold(scaffoldState = scaffoldState)
{
//--------------------(login and sign up button)--------------------//
Row(
modifier = Modifier
.padding(top = 50.dp)
.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically,
) {
if (!state.signUpFormIsVisible && !state.loginFormIsVisible) {
Button(
onClick = {
viewModel.onEvent(event = MainEvent.LoginButtonClick)
},
modifier = Modifier
.padding(10.dp)
.weight(0.5f)
) {
Text(text = stringResource(id = R.string.login))
}
Button(
onClick = { viewModel.onEvent(event = MainEvent.SignUpButtonClick) },
modifier = Modifier
.padding(10.dp)
.weight(0.5f)
) {
Text(text = stringResource(id = R.string.signup))
}
}
}
LoginForm(show = state.loginFormIsVisible) { msg ->
scope.launch {
scaffoldState.snackbarHostState.showSnackbar(
message = msg
)
}
}
SignUpForm(show = state.signUpFormIsVisible) { msg ->
scope.launch {
scaffoldState.snackbarHostState.showSnackbar(
message = msg
)
}
}
}
}
每个登录和注册屏幕都有自己的视图模型,使用方式如下:
@Composable
fun LoginForm(show: Boolean, snackBarMsg: (String) -> Unit) {
val viewModel: LoginViewModel = viewModel()
val state = viewModel.state.value
...
AnimatedVisibility(
visible = show,
enter = slideInVertically(),
exit = slideOutVertically()
) {
...
...
}
}
如果可组合项不可见,视图模型被销毁,我如何将每个视图模型绑定到它的可组合函数?
如果相应的可组合项不可见,销毁视图模型是否是一种好做法?
ViewModel 应该独立于 UI。如果 UI 由于配置更改而被破坏,则视图模型应保持完整并在重组时保留 UI 的状态。将视图模型绑定到每个可组合项是没有意义的。通常,每个屏幕应该只有一个视图模型,并且该屏幕上的所有可组合项都应该使用该视图模型。但这不是硬性规定。当然,有些可组合项可以而且应该有自己的视图模型。但它们需要在更高级别进行管理,以便在它们出现的屏幕不再使用时被销毁。当您使用后退按钮从当前屏幕导航到上一个屏幕时,您通常会希望销毁该屏幕的所有视图模型。
对于 UI 层次结构中的可组合项如何访问视图模型并销毁它们,有更好的方法。我开发了一个解决方案来管理屏幕的视图模型,并允许层次结构中任何位置的可组合项轻松访问它们。还有一个特点是,即使在 return 到上一个屏幕时屏幕被破坏,视图模型也可以保持活动状态。 use-case 如果您正在开发一个主持视频会议的应用程序,并且您希望在不终止会议的情况下返回时继续使用摄像头和音频。为此,viewmodel 需要保持活动状态,即使屏幕本身已被销毁。 Jetpack Compose 有自己的解决方案,但我从未对它们感到满意。本机方法的两个最大缺点是您无法在屏幕之间传递 objects,并且您需要编写代码来检测设备配置设置和更改以适应屏幕。我用我开发的框架解决了这些问题。看看:
在 Compose 中,您可以使用 navigation,这非常适合您的需求:每个路由都有自己的视图模型范围,一旦从导航返回堆栈中删除路由,它就会被销毁。
您可以使用popBackStack
在导航到新屏幕之前从堆栈中移除当前屏幕,旧屏幕将与相应的视图模型一起被销毁。查看 了解如何删除多个项目。
Compose Navigation 基于常规 Android 导航,因此它 documentation 与大多数问题相关,以防 Compose Navigation 文档对您来说太短。
我现在正在使用 Jetpack Compose。 我意识到我可以按照我的可组合和初始化视图模型在可组合中使用 ViewModel,就像这样:
val myViewModel:MyViewModel = viewModel()
但存在一个问题,即这些视图模型永远不会被销毁,即使未显示可组合项也是如此。
例如,我有一个主可组合屏幕,它根据用户交互加载一些其他屏幕,如下所示:
@Composable
fun MainAuthentication(viewModel: MainViewModel) {
val state = viewModel.state.value
val scope = rememberCoroutineScope()
val scaffoldState = rememberScaffoldState()
Scaffold(scaffoldState = scaffoldState)
{
//--------------------(login and sign up button)--------------------//
Row(
modifier = Modifier
.padding(top = 50.dp)
.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically,
) {
if (!state.signUpFormIsVisible && !state.loginFormIsVisible) {
Button(
onClick = {
viewModel.onEvent(event = MainEvent.LoginButtonClick)
},
modifier = Modifier
.padding(10.dp)
.weight(0.5f)
) {
Text(text = stringResource(id = R.string.login))
}
Button(
onClick = { viewModel.onEvent(event = MainEvent.SignUpButtonClick) },
modifier = Modifier
.padding(10.dp)
.weight(0.5f)
) {
Text(text = stringResource(id = R.string.signup))
}
}
}
LoginForm(show = state.loginFormIsVisible) { msg ->
scope.launch {
scaffoldState.snackbarHostState.showSnackbar(
message = msg
)
}
}
SignUpForm(show = state.signUpFormIsVisible) { msg ->
scope.launch {
scaffoldState.snackbarHostState.showSnackbar(
message = msg
)
}
}
}
}
每个登录和注册屏幕都有自己的视图模型,使用方式如下:
@Composable
fun LoginForm(show: Boolean, snackBarMsg: (String) -> Unit) {
val viewModel: LoginViewModel = viewModel()
val state = viewModel.state.value
...
AnimatedVisibility(
visible = show,
enter = slideInVertically(),
exit = slideOutVertically()
) {
...
...
}
}
如果可组合项不可见,视图模型被销毁,我如何将每个视图模型绑定到它的可组合函数?
如果相应的可组合项不可见,销毁视图模型是否是一种好做法?
ViewModel 应该独立于 UI。如果 UI 由于配置更改而被破坏,则视图模型应保持完整并在重组时保留 UI 的状态。将视图模型绑定到每个可组合项是没有意义的。通常,每个屏幕应该只有一个视图模型,并且该屏幕上的所有可组合项都应该使用该视图模型。但这不是硬性规定。当然,有些可组合项可以而且应该有自己的视图模型。但它们需要在更高级别进行管理,以便在它们出现的屏幕不再使用时被销毁。当您使用后退按钮从当前屏幕导航到上一个屏幕时,您通常会希望销毁该屏幕的所有视图模型。
对于 UI 层次结构中的可组合项如何访问视图模型并销毁它们,有更好的方法。我开发了一个解决方案来管理屏幕的视图模型,并允许层次结构中任何位置的可组合项轻松访问它们。还有一个特点是,即使在 return 到上一个屏幕时屏幕被破坏,视图模型也可以保持活动状态。 use-case 如果您正在开发一个主持视频会议的应用程序,并且您希望在不终止会议的情况下返回时继续使用摄像头和音频。为此,viewmodel 需要保持活动状态,即使屏幕本身已被销毁。 Jetpack Compose 有自己的解决方案,但我从未对它们感到满意。本机方法的两个最大缺点是您无法在屏幕之间传递 objects,并且您需要编写代码来检测设备配置设置和更改以适应屏幕。我用我开发的框架解决了这些问题。看看:
在 Compose 中,您可以使用 navigation,这非常适合您的需求:每个路由都有自己的视图模型范围,一旦从导航返回堆栈中删除路由,它就会被销毁。
您可以使用popBackStack
在导航到新屏幕之前从堆栈中移除当前屏幕,旧屏幕将与相应的视图模型一起被销毁。查看
Compose Navigation 基于常规 Android 导航,因此它 documentation 与大多数问题相关,以防 Compose Navigation 文档对您来说太短。