Jetpack Compose 在多个页面之间传递 ViewModel 但在超出范围时删除

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

这就是我想要实现的目标(注意我对 Jetpack Compose 还很陌生):

Jetpack Compose 2022 年 4 月。日期很重要,因为我正在寻找最新的最佳实践。 我尝试了很多不同的例子,但它们要么不起作用,要么现在被认为是不好的做法。

用例:

  1. MainActivity:执行任何基本设置,然后使用 NavHost 打开名为“HomePage”的第一页。
  2. HomePage 有多个按钮可以关闭并执行不同的任务。
  3. 这些任务中的第一个(这里只考虑一个)是使用共享 ViewModel 的多页面表单(多个页面)。我们称它们为“FormPageViewModel”、“FormPage1”、“FormPage2”和“FormPage3”。
  4. 所以在主页上我点击了 ;Create Form 按钮,我现在正在查看 FormPage1。现在在那里我可以做一些我想做的事情:
    // Create a new one or find one that already exist (it shouldn’t at this point for note).
    val formPageViewModel = ViewModel<FormPageViewModel>()
  1. 现在我在填写内容的表单页面中来回移动。我可能会单击取消(甚至是 FormPage1 中的后退按钮)按钮返回主页,或者我可能会在最后一页上使用一个按钮完成表单,然后保存它到 json(这里不相关)并返回到主页。

当我返回主页时,我正在努力摆脱 FormPageViewModel 的“销毁/删除/删除/您的首选术语”。基本上它应该就好像它从未被实例化过一样,只有当我们再次打开 FormPage1 时才会被实例化。

正如我在上面已经说过的那样,我已经从一些似乎不再有效或被认为是不好的做法的示例中尝试了很多方法。我争论过创建两个活动,但即使这样似乎也不受欢迎(尽管它确实有效)。

我还可以在 Activity 中创建 ViewModel 并将其传递给带有导航代码的可组合项,甚至可能会破坏 ViewModel 但这看起来非常混乱,而且在我正在阅读的一些地方似乎又不受欢迎。

目前我也在尝试避免使用 Hilt 和 DI,直到我从原始(ish)的角度了解如何去做以及在添加更多抽象之前发生了什么。

android-jetpack-compose viewmodel android-jetpack android-jetpack-navigation jetpack-compose-navigation
2个回答
0
投票

在 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
限定在您的活动范围内。


0
投票

到目前为止,这就是我想出的。

它是否遵循 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
                    )
                }
            }
        }

    )
}

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