这就是我想要实现的目标(注意我对 Jetpack Compose 还很陌生):
Jetpack Compose 2022 年 4 月。日期很重要,因为我正在寻找最新的最佳实践。 我尝试了很多不同的例子,但它们要么不起作用,要么现在被认为是不好的做法。
用例:
// Create a new one or find one that already exist (it shouldn’t at this point for note).
val formPageViewModel = ViewModel<FormPageViewModel>()
当我返回主页时,我正在努力摆脱 FormPageViewModel 的“销毁/删除/删除/您的首选术语”。基本上它应该就好像它从未被实例化过一样,只有当我们再次打开 FormPage1 时才会被实例化。
正如我在上面已经说过的那样,我已经从一些似乎不再有效或被认为是不好的做法的示例中尝试了很多方法。我争论过创建两个活动,但即使这样似乎也不受欢迎(尽管它确实有效)。
我还可以在 Activity 中创建 ViewModel 并将其传递给带有导航代码的可组合项,甚至可能会破坏 ViewModel 但这看起来非常混乱,而且在我正在阅读的一些地方似乎又不受欢迎。
目前我也在尝试避免使用 Hilt 和 DI,直到我从原始(ish)的角度了解如何去做以及在添加更多抽象之前发生了什么。
在 compose 中,你需要考虑不同的观点,将生命周期视为 Activity 或 Fragments,这些可以通过委托使用
activityViewModels
或 viewModels
附加视图模型,在 compose 中,可组合项的生命周期是它进入的时间组合,重新组合并退出组合。
这是基于事件/状态的,您需要保留组合发生一次的实例,以防止实例重复。
当你创建你的
NavigationGraph
时,你将只创建一次这个可组合项,然后你可以在其中创建你的视图模型并将它传递到你需要这个视图模型的每个 composable(route)
函数中。
在组合中,我们可以使用称为
DisposableEffect
的东西,它将在 onDispose 中触发(当可组合项离开组合时)
此时,您可以在返回时删除此可组合项保存在视图模型中的所有数据。但是你可以为他们两个保留相同的实例。
你可以在这里阅读https://developer.android.com/jetpack/compose/side-effects#disposableeffect
NavigationGraph(navController: NavigationController) {
val viewmodel: SharedViewmodel = viewModel()
NavHost(...) {
composable(route1) {
YourFirstComposable(viewmodel)
}
composable(route2) {
YourSecondComposable(viewmodel)
}
}
在使用此视图模型的每个目的地内,使用
DisposableEffect
删除视图模型中的数据,进行清理等
正如您在上面看到的,没有必要将您的
sharedViewmodel
限定在您的活动范围内。
到目前为止,这就是我想出的。
它是否遵循 Android 最佳实践。我不确定是否诚实。
但它在 2023 年 4 月确实有效。
如果有人有任何更好的想法或改进,我洗耳恭听。
我不打算将此标记为答案,因为实际上我不确定这是否有点笨拙或有缺陷。
我还没有注意到任何内存泄漏等。
注意我已经删除了部分样板代码以尝试使其更清晰易读。
MainActivity.
sealed class View(val route: String) {
object HomeView : View("home_view")
object StepOneFormView : View("step_one_form_view")
object StepTwoFormView : View("step_two_form_view")
}
/// Other Activity Code...
@Composable
fun NavigationAppHost(mainActivity: MainActivity) {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home_view") {
composable(route = View.HomeView.route) {
HomeView(
navController = navController
)
}
// Seperate the Form Route from our HomeView so to speak.
navigation(
startDestination = "step_one_form_view",
route = "form_route"
) {
composable(route = View.StepOneFormView.route) {
StepOneFormView(
navController = navController
)
}
composable(route = View.StepTwoFormView.route) {
StepTwoFormView(
navController = navController
)
}
}
}
}
SharedFormViewModel.kt
class SharedFormViewModel : ViewModel() {
// StepOneFormView.
var stepOneInputField by mutableStateOf("")
///////////////////////////////////////////////////////////////////////////////////////////////
// StepTwoFormView.
var stepTwoInputField by mutableStateOf("")
}
StepOneFormView.kt
@Composable
fun StepOneFormView(
navController: NavHostController
) {
// Instantiate the view model if it doesnt exist.
// Get a handle to it if it does already exist.
val sharedFormViewModel: SharedFormViewModel = viewModel()
Scaffold(
topBar = {
CenterAlignedTopAppBar (
title = {Text(text = "ONE")},
navigationIcon = {
IconButton(onClick = {
navController.navigateUp()
}) {
Icon(Icons.Rounded.ArrowBack, "Back")
}
},
actions = {
IconButton(onClick = {
// I want the Instantiated sharedFormViewModel destroyed
// Before returning to the Home View.
navController.navigate(View.HomeView.route) {
popUpTo(View.HomeView.route) { inclusive = true }
}
}) {
Icon(Icons.Rounded.Close, "Cancel")
}
},
colors
= TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
navigationIconContentColor = Color.White,
titleContentColor = Color.White,
actionIconContentColor = Color.White
)
)
},
content = {
// Formatting and code removed for clarity.
Column() {
OutlinedTextField(
value = sharedFormViewModel.stepOneInputField,
onValueChange = { sharedFormViewModel.stepOneInputField = it },
label = {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = "Step One Input Field")
}
},
// Extra code removed for clarity.
)
Spacer(modifier = Modifier.height(18.dp))
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
navController.navigate(View.StepTwoFormView.route)
}
) {
Text(
text = "Next",
textAlign = TextAlign.Center,
fontSize = 22.sp
)
}
}
}
)
}
StepTwoFormView.kt
@Composable
fun StepTwoFormView(
navController: NavHostController
) {
// Instantiate the view model if it doesnt exist.
// Get a handle to it if it does already exist.
// HERE it should already exist
val sharedFormViewModel: SharedFormViewModel = viewModel()
// This is specific to my requirement.
// I want the user to be able to move backwards and forwards between
// form input views with out losing data from pages further along.
// So if I navigate back to the StepOneFormView I want any input here
// to be saved when StepTwoFormView is opened again.
val stepTwoInputField = rememberSaveable {
mutableStateOf(sharedFormViewModel.stepTwoInputField)
}
Scaffold(
topBar = {
CenterAlignedTopAppBar (
title = {Text(text = "TWO")},
navigationIcon = {
IconButton(onClick = {
navController.navigateUp()
}) {
Icon(Icons.Rounded.ArrowBack, "Back")
}
},
actions = {
IconButton(onClick = {
// I want the Instantiated sharedFormViewModel destroyed
// Before returning to the Home View.
navController.navigate(View.HomeView.route) {
popUpTo(View.HomeView.route) { inclusive = true }
}
}) {
Icon(Icons.Rounded.Close, "Cancel")
}
},
colors
= TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
navigationIconContentColor = Color.White,
titleContentColor = Color.White,
actionIconContentColor = Color.White
)
)
},
content = {
// Formatting and code removed for clarity.
Column() {
// Note this is slightly different to the input on the StepOneFormView.
OutlinedTextField(
value = stepTwoInputField.value,
onValueChange = {
stepTwoInputField.value = it
patientFormViewModel.stepTwoInputField = it
},
label = {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = "Step Two Input Field")
}
},
// Extra code removed for clarity.
)
Spacer(modifier = Modifier.height(18.dp))
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
// Go off and save the Form somewhere...
}
) {
Text(
text = "Next",
textAlign = TextAlign.Center,
fontSize = 22.sp
)
}
}
}
)
}