Search code examples
kotlinviewmodelandroid-jetpack-composeandroid-lifecycle

how to bind ViewModel life cycle to compose


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?


Solution

  • 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.