리스트 데이터를 보여주고, 항목의 세부 정보를 다른 화면에서 표시할 수 있도록 이동하는 기능은 흔히 요구되는 기능이다. 하지만 이 과정에서 새로운 화면의 ViewModel에서 다시 API를 호출하거나, 데이터를 데이터베이스에 저장하고 싶지 않은 경우도 많다.
이런 상황에서는 공유 ViewModel(shared ViewModel)이 최적의 해결책이 될 수 있다.
이미 가져온 데이터를 재사용하고 새로운 ViewModel 생성을 방지함으로써 구현을 최적화할 수 있기 때문이다.
Jetpack Compose 이전에는 공유 ViewModel을 구현하는 방식이 달랐다. Jetpack Compose에서는 구현 방식이 변경되었다.
예시로, Screen A는 ViewModel A를 통해 데이터를 가져와 리스트를 보여주고, Screen B는 각 항목의 세부 정보를 표시한다고 가정해 보겠다. 보통 Hilt를 사용한 ViewModel 구현 및 Jetpack Compose에서 네비게이션을 설정할 때는 다음과 같은 방식을 따른다.
@Composable
fun ScreenA(
navigateToScreenB: () -> Unit,
viewModel: AViewModel = hiltViewModel()
) {
// ...
}
@HiltViewModel
class AViewModel @Inject constructor(
// ...
) : ViewModel() {
// ...
}
@Composable
fun ScreenB(
viewModel: AViewModel = hiltViewModel()
) {
// ...
}
composable("ScreenA") {
ScreenA(
navigateToScreenB = {
navController.navigate("ScreenB")
}
)
}
composable("ScreenB") {
ScreenB()
}
위 방식은 일반적인 경우에는 문제가 없지만, 공유 ViewModel을 사용하고자 할 때는 문제가 발생한다. 왜냐하면 Screen B로 이동할 때 AViewModel의 새로운 인스턴스가 생성되며, 이로 인해 AViewModel의 init 블록이 다시 실행되기 때문이다.
이는 불필요한 리소스 소모를 초래할 수 있다.
Jetpack Compose에서는 navBackStackEntry를 활용해 네비게이션 스택이 ViewModel의 상태를 인지하도록 설정하여 불필요한 재생성을 방지할 수 있다. 이를 통해 네비게이션 스택에 이미 ViewModel이 존재함을 알리고, 기존의 ViewModel을 재사용하도록 만들 수 있다.
@Composable
fun ScreenA(
navigateToScreenB: () -> Unit,
viewModel: AViewModel
) {
// ...
}
@HiltViewModel
class AViewModel @Inject constructor(
// ...
) : ViewModel() {
// ...
}
@Composable
fun ScreenB(
viewModel: AViewModel
) {
// ...
}
composable("ScreenA") { backStackEntry ->
val viewModel: AViewModel = hiltViewModel(backStackEntry)
ScreenA(
viewModel = viewModel,
navigateToScreenB = {
navController.navigate("ScreenB")
}
)
}
composable("ScreenB") { backStackEntry ->
val viewModel: AViewModel =
if (navController.previousBackStackEntry != null) {
hiltViewModel(navController.previousBackStackEntry!!)
} else {
hiltViewModel()
}
ScreenB(
viewModel = viewModel
)
}
composable("ScreenA")composable("ScreenA"): NavHost에 등록된 경로 "ScreenA"에 해당하는 화면을 정의한다.hiltViewModel(backStackEntry): 이 함수는 backStackEntry를 기반으로 AViewModel의 인스턴스를 생성하거나 기존 인스턴스를 반환한다.backStackEntry는 ScreenA의 네비게이션 상태를 나타내며, 이를 통해 ViewModel의 재사용이 가능해진다.AViewModel이 생성되고, ScreenA에서 이를 활용해 데이터를 관리한다.ScreenA: ScreenA Composable은 생성된 AViewModel을 전달받아 UI를 렌더링한다.navigateToScreenB: 클릭 시 navController.navigate("ScreenB")를 호출하여 ScreenB로 이동한다.composable("ScreenB")composable("ScreenB"): NavHost에 등록된 경로 "ScreenB"에 해당하는 화면을 정의한다.if (navController.previousBackStackEntry != null):previousBackStackEntry: NavController의 이전 백스택 항목을 나타낸다."ScreenA"에서의 backStackEntry를 의미한다.ScreenB에서 AViewModel을 공유하려면 이전 백스택 항목의 ViewModel을 가져와야 한다.previousBackStackEntry가 존재할 경우: ScreenA의 backStackEntry를 활용해 기존의 AViewModel을 가져온다.AViewModel을 생성한다.ScreenB: ScreenB Composable은 전달받은 AViewModel을 사용해 UI를 렌더링한다.hiltViewModel(backStackEntry): 특정 backStackEntry를 기반으로 ViewModel을 생성하거나 반환한다.ScreenA에서 생성된 ViewModel을 ScreenB에서도 공유하도록 설정.navController.previousBackStackEntry:ScreenB로 이동했을 때, 이전 화면(ScreenA)의 backStackEntry를 참조해 기존 ViewModel을 재사용한다.AViewModel을 ScreenA와 ScreenB에서 공유하여 API 호출이나 초기화 작업을 반복하지 않아도 된다.ScreenA를 탐색하면 AViewModel이 생성된다.ScreenB로 이동 시, AViewModel의 기존 인스턴스를 ScreenA에서 가져와 재사용한다.https://medium.com/@mousaieparniyan/shared-viewmodel-in-jetpack-compose-1328f5c895c5