Home > database >  how to bind ViewModel life cycle to compose
how to bind ViewModel life cycle to compose

Time:11-29

I'm using Jetpack Compose now. I realized I could have ViewModel per my composable and init view model in composable just like this:

val myViewModel:MyViewModel = viewModel()

But there is a problem that these view models will never be destroyed, even when the composable is not shown.

for instance, I have a Main composable screen that loads some other screen based on user interaction, like this:

@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
                )
            }
        }


    }
}

each of the login and sign up screens has its view model and is used like this:

@Composable
fun LoginForm(show: Boolean, snackBarMsg: (String) -> Unit) {

    val viewModel: LoginViewModel = viewModel()
    val state = viewModel.state.value
 ...

AnimatedVisibility(
        visible = show,
        enter = slideInVertically(),
        exit = slideOutVertically()
    ) {
    ...
    ...
    }
}

How can I bind each view model to its composable function if the composable is not visible, the view model gets destroyed?

Is it a good practice to destroy the view models if the respective composable is not visible?

CodePudding user response:

In Compose you can use navigation, which is perfect for your needs: each route has its own view model scope, which is destroyed as soon as the route is removed from the navigation back stack.

You can use popBackStack to remove current screen from the stack before navigation to the new screen, the old screen will be destroyed with the corresponding view model. Check out this answer on how you can remove multiple items.

Compose Navigation is based on regular Android navigation, so its documentation is relevant for most of questions, in case the Compose Navigation documentation seems short to you.

CodePudding user response:

ViewModels are meant to be independent of the UIs. If the UI gets destroyed due to a configuration change, the viewmodel should remain intact and retain the state of the UI when it gets recomposed. Binding a viewmodel to every composable makes no sense. Normally, you should have only one viewmodel per screen and all the composables on that screen should use that one. But that isn't a hard rule. There are certainly composables that can and should have their own viewmodels. But they need to be managed at a higher level so that they get destroyed when the screen they appear on is no longer in use. When you navigate from the current screen to a previous screen using the Back button, you normally will want to have all the viewmodels for that screen destroyed.

There are better approaches to how composables in your UI hierarchy can access viewmodels and have them destroyed. I developed one solution that manages your viewmodels for your screens and allows composables anywhere within the hierarchy to easily access them. There is also the feature that a viewmodel can stay alive even if the screen is destroyed when you return to the previous screen. A use-case for this is if you are developing an app that hosts video conferences and you want the camera and audio to continue while you navigate back without terminating the meeting. For this, the viewmodel needs to remain alive, even though the screen itself has been destroyed. Jetpack Compose has its own solutions but I was never satisfied with them. The two biggest drawbacks with the native approach is that you cannot pass objects between screens and you need to write code to detect device configuration settings and changes to adapt the screen. I solved these with the framework I developed. Check it out:

https://github.com/JohannBlake/Jetmagic

  • Related