[Compose] viewmodel을 다른 Screen에서 공유하기

같은 로직을 공유하는 화면에서는 종종 동일한 viewmodel 인스턴스를 공유해야 하는 일이 생긴다.

compose를 사용하지 않던 기존 viewmodel에서는 ActivityViewModel을 사용하여 Fragment간 Viewmodel을 공유했으나 
compose로 바뀌면서 화면단위가 Screen으로 변경되어 방법이 달라졌다.

 

결론적으로 NavBackStackEntry를 사용한다.

NavBackStackEntry는 네비게이션 백스택의 각 항목을 나타내며, 각 항목에 ViewModelStore가 포함되어 있다. 따라서 특정 화면의 

viewModel을 가져오고 싶다면 NavBackStackEntry를 사용하여 해당 화면에 viewModel 인스턴스에 접근하면 된다.

 

방법은 다음과 같다.

 

먼저 NavHost에서 각 화면을 호출할 때 인자로 viewModel을 넣어주었다.

composableWithAnimation(route = ConveniiScreen.Register1.name) { backStackEntry ->
    val parentEntry = remember(backStackEntry) {
        navController.getBackStackEntry(ConveniiScreen.SignIn.name)
    }
    Register1Screen(
        navController = navController,
        viewModel = hiltViewModel(parentEntry)
    )
}

composableWithAnimation(route = ConveniiScreen.Register2.name) { backStackEntry ->
    val parentEntry = remember(backStackEntry) {
        navController.getBackStackEntry(ConveniiScreen.SignIn.name)
    }
    Register2Screen(
        navController = navController,
        viewModel = hiltViewModel(parentEntry)

    )
}

해당 예시에서는 Register1 Screen 과 Register2 Screen이 동일한 viewModel을 공유해야하기 때문에 동일하게 SignIn Screen과 

연결된 NavBackStackEntry를 제공하는 것을 볼 수 있다.

필자는 hilt를 사용하고 있기 때문에 viewModel인자에 hiltViewModel(parentEntry)를 제공하는 것을 볼 수 있다.

 

ViewModel을 사용하는 Register2화면은 다음과 같이 작성되었다.

@Composable
fun Register2Screen(
    navController: NavController,
    viewModel: RegisterViewModel
) {
    val email by viewModel.email.collectAsState()
    var code by remember { mutableStateOf("") }
    val isEmailCheck by viewModel.isEmailCheck.collectAsState()

    LaunchedEffect(key1 = isEmailCheck) {
        if (isEmailCheck is APIResponse.Success) {
            navController.navigate("Register3")
            viewModel.resetIsEmailCheck()
        }
    }
    Scaffold(
    ...

Register2 Screen의 인자로 RegisterViewModel을 받고있고 이는 앞에서 전달해준 hiltViewModel이다.

이 ViewModel은 Register1Screen과 같은 인스턴스의 ViewModel이다.