Skip to content

⚙ [기술 분석] ViewModel 공유

Moony-H edited this page Dec 5, 2024 · 2 revisions

❌ 문제 상황

검색 기능을 구현할 때, paging으로 받아온 게시물을 누르고, Vertical Pager로 구현된 Detail Screen으로 이동했을 때, 다시 Paging3 라이브러리를 사용해서 게시물을 받아온다면, 불필요한 로딩 시간으로 인해 사용자들이 불편함을 느낄 것이다. 이를 위해 두 개의 screen에서 Paging Data를 재사용할 수 있도록 ViewModel을 공유하고자 했다.

🔍NavBackStackEntry

뷰모델을 공유하기 위해 먼저 NavBackStackEntry의 역할을 알아야한다. NavBackStackEntry는 현재 네비게이션 스택에서 특정 목적지의 상태를 나타낸다. NavBackStackEntry에는 화면 자체의 정보(destination), 전달 데이터(arguments), 생명주기(lifecycle), 상태 저장/복원(savedState) ,화면과 관련된 뷰모델(viewModel) 를 포함한다.

즉, NavBackStackEntry는 해당 라우트의 생명 주기를 관리하는 역할을 한다.

image

🔍HiltViewModel

Porring에서는 HiltViewModel을 사용하고 있다. hiltViewModel의 내부 코드를 보면 viewModelStoreOwner을 필요로 하고 있는 것을 볼 수 있다. ViewModel은 ViewModelStoreOwner의 생명주기를 따른다. 예를 들어, activity가 소멸되면 해당 acivity에 연결된 viewmodel도 소멸된다.

@Composable
public inline fun <reified VM : ViewModel> hiltViewModel(
    viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner. current) {         "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"     },
    key: String? = null
): VM

Jetpack Compose에서 NavBackStackEntry를 사용하면, 해당 목적지에 연결된 ViewModel을 생성할 수 있다. NavBackEntry가 ViewModelStoreOwner의 역할을 수행한다고 볼 수 있는데, 이렇게 한다면, 해당 목적지(화면)가 활성화되어 있는 동안에만 ViewModel이 유지된다.

프로젝트 적용

navBackStackEntry = { navigator.navController.getBackStackEntry(MainMenu.Home.route) }
			        
fun NavGraphBuilder.searchNavGraph(
    padding: PaddingValues,
    navigateToTheir: (String) -> Unit,
    navigateToSearchDetail: () -> Unit,
    popBackStack: () -> Unit,
    getBackStackEntry: () -> NavBackStackEntry
) {
    composable<MainMenuRoute.Search> { backStackEntry ->
        val parentEntry = remember(backStackEntry) {
            getBackStackEntry()
        }
        SearchRoute(
            padding = padding,
            viewModel = hiltViewModel(parentEntry),
            navigateToSearchDetail = navigateToSearchDetail
        )
    }

    composable<Route.DetailSearch> { backStackEntry ->
        val parentEntry = remember(backStackEntry) {
            getBackStackEntry()
        }
        DetailSearchRoute(
            padding = padding,
            viewModel = hiltViewModel(parentEntry),
            navigateToTheir = navigateToTheir,
            popBackStack = popBackStack
        )
    }
}

위의 코드를 보면, getBackStackEntry 메서드를 통해 “Home” 라우트에 대한 NavBackStackEntry를 가져온다.

hiltViewModel(parentEntry)를 사용하며 searchViewmodel을 생성할 때, 이 ViewModel은 parentEntry의 생명주기에 따라 관리된다. 즉, “Home" 라우트가 활성화되어 있는 동안에만 ViewModel이 생성되고, "Home" 라우트가 사라지면 ViewModel도 소멸된다.

동일한 인스턴스 공유

두 개의 composable에서 각각 hiltviewmodel을 호출하면, 동일한 NavBackStackEntry를 참조하므로 동일한 ParentViewModel 인스턴스를 가져온다. 이로 인해 SearchRoute와 DetailSearchRoute가 동일한 Viewmodel 상태를 공유하게 된다. 실제로 두 screen이 같은 뷰모델을 공유하는지 확인해본 결과, 아래 로그 사진과 같이 정상적으로 동일 인스턴스를 공유하는 것을 확인했다.

image

Clone this wiki locally