【发布时间】:2021-09-05 14:06:00
【问题描述】:
我刚开始使用 Compose。乍一看,对我来说,这一切都像是我喜欢的 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()
}
}
当然,我的实际情况要复杂得多,我尽了最大努力将其简化,但这已经说明了我的问题:
- 在两个屏幕(可组合)之间导航并旋转屏幕
- 并观察 LogCat 中两个视图模型的 Created/Destroyed 消息
- 首先:从一个屏幕导航到另一个屏幕时永远不会调用 Destroyed(显然是因为 Activity 保持活动状态),这在大型项目中是完全不能接受的
- 随后,只要您至少导航一次到另一个屏幕(只需点击按钮),每次屏幕旋转都会开始重新创建视图模型,这也是完全不可接受的
我知道 compose 还不成熟(我看到一些组件仍处于“alpha”版本)。所以这可能是撰写本身的一个错误。
或者这可能只是我对如何在大型和模块化项目中使用 Compose 的误解......
有什么想法吗?
(为了完整起见,我仔细检查了我使用的是当前可用的最新版本。)
编辑 #1 (2021/09/05)
感谢处理我的一个问题的文章(下面的评论中的链接),我解决了其中一个问题:在旋转屏幕时不再重新创建视图模型(仍然不知道为什么)。
所以剩下的问题是视图模型没有遵循预期的生命周期。
编辑 #2 (2021/09/13)
感谢下面的答案(不幸的是,我没有找到任何方法让它接受答案 - SF UI 对我来说仍然有点不清楚),我能够真正让视图模型生命周期按预期工作。
我刚刚禁用了后台堆栈,这在我的应用程序中无论如何都是不需要的(在 UI 和底层模型之间造成很多混乱)功能...
【问题讨论】:
-
同时,我发现这篇有趣的文章似乎在处理我在 Compose 中的一个架构问题(显然,幸运的是,我不是唯一一个):medium.com/google-developer-experts/…跨度>
标签: android kotlin android-jetpack-compose