리스트 데이터를 보여주고, 항목의 세부 정보를 다른 화면에서 표시할 수 있도록 이동하는 기능은 흔히 요구되는 기능이다. 하지만 이 과정에서 새로운 화면의 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