问题描述
我才刚刚开始作曲。乍一看,对我来说,它看起来就像是我喜欢的SwiftUI的副本。但当我开始真正使用它时,我很快就遇到了很多问题。显然,我需要找到正确的方式来使用它以从中受益...
这是我的一个问题。
package org.test.android.kotlin.compose.ui
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import org.test.android.kotlin.compose.ui.theme.MbiKtTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MbiKtTheme {
val navController = rememberNavController()
// <Edit #1>
// Navigator.route.collectAsState("").value.takeIf { it.isNotEmpty() }?.also { navController.navigate(it) }
// Navigator.route.observe(this, { route -> navController.navigate(route) })
// </Edit #1>
// <Edit #2>
Navigator.route.collectAsState("").value.takeIf { it.isNotEmpty() }?.also {
navController.popBackStack()
navController.navigate(it)
}
// </Edit #2>
Surface(color = MaterialTheme.colors.background) {
NavHost(
navController = navController,
startDestination = "setup"
) {
composable(route = "setup") {
SetupScreen()
}
composable(route = "progress") {
ProgressScreen()
}
}
}
}
}
}
}
// This is unnecessary here in this simple code fragment, but a MUST for large modular projects
object Navigator {
// <Edit #1>
val route = MutableSharedFlow<String>(0, 1, BufferOverflow.DROP_OLDEST)
//val route: MutableLiveData<String> = MutableLiveData()
// </Edit #1>
}
class SetupViewModel : ViewModel() {
init {
Log.d(toString(), "Create")
}
override fun onCleared() {
Log.d(toString(), "Destroy")
}
override fun toString(): String {
return "SetupViewModel"
}
}
@Composable
fun SetupScreen(model: SetupViewModel = viewModel()) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(all = Dp(8f))
) {
Text(text = "Setup")
Spacer(modifier = Modifier.weight(1f))
Button(onClick = { Navigator.route.tryEmit("progress") }, modifier = Modifier.fillMaxWidth()) { Text(text = "Register") }
}
}
class ProgressViewModel : ViewModel() {
init {
Log.d(toString(), "Created")
}
override fun onCleared() {
Log.d(toString(), "Cleared")
}
override fun toString(): String {
return "ProgressViewModel"
}
}
@Composable
fun ProgressScreen(model: ProgressViewModel = viewModel()) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(all = Dp(8f))
) {
Text(text = "Progress")
Spacer(modifier = Modifier.weight(1f))
Button(onClick = { Navigator.route.tryEmit("setup") }, modifier = Modifier.fillMaxWidth()) { Text(text = "Abort") }
}
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
MbiKtTheme {
SetupScreen()
}
}
我的现实当然要复杂得多,我尽了最大努力将它尽可能地简化,但这已经证明了我的问题:
- 在两个屏幕(可合成的)之间导航并旋转屏幕
- 并在日志中查看来自两个视图模型的已创建/已销毁消息
- 首先:从一个屏幕导航到另一个屏幕时决不会调用Debled(显然是因为活动保持活动状态),这在大型项目中是完全不能接受的
- 随后,只要您至少导航到另一个屏幕一次(只需轻触按钮),每次屏幕旋转就会开始重新创建视图模型,这也是完全不可接受的
我知道Compose还不成熟(我已经看到一些组件仍在Alpha&Quot;发行版中)。因此,这可能是作文本身存在错误。
或者这可能只是我对如何在大型和模块化项目中使用Compose的理解错误...
有什么想法吗?
(仅为完整起见,我再次确认我使用的是所有内容的当前最新版本。)
编辑#1(2021/09/05)
多亏了那篇关于我的一个问题的文章(下面评论中的链接),我修复了其中一个问题:在旋转屏幕时不再重新创建视图模型(仍然没有线索,原因是什么)。
因此,剩下的问题是视图模型没有遵循预期的生命周期。
编辑#2(2021/09/13)
多亏了下面的答案(不幸的是,我没有找到任何方法让它被接受-SF UI对我来说仍然有点不清楚),我能够真正使视图模型的生命周期按预期工作。
我刚刚禁用了后台堆栈,这在我的应用程序中无论如何都是不需要的(在UI和底层模型之间造成了很多混乱)功能...
推荐答案
每次旋转屏幕时都会重新生成完整的合成树,从setContent
开始。
在源代码中,您在每次重新组合时都订阅Navigator.route.observe
。而&修复&则是将LiveData
或Float
转换为复合状态。您使用Flow
+collectAsState
实现了这一点,对于LiveData
,一个类似的方法称为observeAsState
。了解有关state in compose的更多信息。
所以,每次您旋转设备时,navigate
都会被调用。
navigate
不会使用新目标更改当前屏幕。相反,它会将新视图推送到堆栈上。因此,每次navigate
,您都会将一个新屏幕推到导航堆栈上,并为其创建一个模型。当您在没有collectAsState
的情况下旋转设备时,您会将另一个屏幕推送到堆栈上。在documentation中查看有关撰写导航的更多信息。
您可以使用NavOptionsBuilder
更改此行为,例如:
navController.navigate(route) {
if (route == "setup") {
popUpTo("setup")
}
}
当相应的视图离开导航堆栈时,将释放视图模型。如果单击导航栏上的"上一步"按钮,您将看到它已被释放。
附注:我个人发现Compose比SwiftUI更灵活、更方便,尽管第一个稳定的版本一个月前才发布。你只需要更好地了解它。
这篇关于Android组合导航和ViewModel生命周期的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!