Android/Today I Learned

Compose Navigation - viewmodel 사용할 때 주의할 점

Nanamare 2021. 12. 25. 22:31
728x90
회사 선임분에게 감사의 인사를

 

최근에 Compose 에서 viewModel 을 파라미터로 넘겨서 사용할 때, 편의를 위해 compositionLocalOf 메서드를 사용하여  같은 Composable Scope 안에서는 currentViewModel() 과 같이 호출하여 당시에 제공하는 Composable Scope 에 맞는 ViewModelStoreOwner 를 넣어주고 viewModel 을 사용할 수 있도록 구성하여 사용하고 있습니다. 

compositionLocalOf 를 사용할 때, Theme 등의 전파의 개념에만 사용하는 것을  구글 가이드에서는 추천하고 있기 때문에, Interface 형식(AAC 의 의존성도 제거할겸)으로 만들어 사용하고 있습니다. 

ViewModel Interface 과 compositionLocalOf 정의
CompositionLocalProvider 로 제공
실제로 사용하는 부분

별문제 없이 사용하다가, 특정 상황에서 currentViewModel 인스턴스가 새롭게 생기는 일이 발생하였습니다.

 

특정상황은 아래 이미지와 같이 Compose Navigation 을 사용할 때, composable 함수 안에서 인스턴스가 새롭게 생겼습니다. 

(Compose 가 아닌 기존의 Jetpack Navigation component 를 사용할 때 Fragment 안에서 viewModel 를 선언해서 사용하면 해당 Fragment 에 대해서만 생기는 것과 같다고 생각하시면 됩니다. 따라서 우리는 경우에 따라서 activityViewModel 같은 것을 만들어서 사용해야 합니다)

Navigation 의 composable 함수

 

알고보니, Compose NavHost 의 코드중 LocalOwnersProvider 라는 함수안에서 LocalViewModelStoreOwner 를 this 로 다시 제공하며 ViewModelStoreOwner 가 변경되어 새로운 인스턴스로 제공 되고 있었습니다.

 

이를 해결하기 위해서는 여러가지 방법이 있을 것 같은데, 우선은 currentViewModel() 로 사용할때 그때그때 Factory 로 Scope 에 맞는 viewModel 을 제공하는 것이 아닌, 한번만 만들어서 사용하는 식으로 변경하였습니다.

 

또는 

val viewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
    "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
}

val navController = rememberNavController()
NavHost(navController = navController, startDestination = "first") {
    composable("first") {
    	// 이런식으로 viewModel 에 owner 를 직접 넣어줄 수도 있습니다.
        val model = viewModel<SharedModel>(viewModelStoreOwner = viewModelStoreOwner)
    }
    composable("second") {
	    // 혹은 CompositionLocalProvider 를 사용하여 viewModelStoreOwner 를 제공해줄수도 있습니다.
        CompositionLocalProvider(
            LocalViewModelStoreOwner provides viewModelStoreOwner
        ) {
            SecondScreen()
        }
    }
}

위와 같이 viewModel 에 직접 넣어줄 수도 있고, viewModelStoreOwner 를 CompositionLocalProvider 를 통해 전파시켜줄수도 있습니다.

 

그럼 20000 :)

728x90