ViewModel 공유하기

이윤설·2024년 12월 11일
0

안드로이드 연구소

목록 보기
10/33

Jetpack Compose에서 ViewModel 공유를 활용한 효율적인 데이터 관리 방법

리스트 데이터를 보여주고, 항목의 세부 정보를 다른 화면에서 표시할 수 있도록 이동하는 기능은 흔히 요구되는 기능이다. 하지만 이 과정에서 새로운 화면의 ViewModel에서 다시 API를 호출하거나, 데이터를 데이터베이스에 저장하고 싶지 않은 경우도 많다.

이런 상황에서는 공유 ViewModel(shared ViewModel)이 최적의 해결책이 될 수 있다.
이미 가져온 데이터를 재사용하고 새로운 ViewModel 생성을 방지함으로써 구현을 최적화할 수 있기 때문이다.

Jetpack Compose 이전 방식과의 차이점

Jetpack Compose 이전에는 공유 ViewModel을 구현하는 방식이 달랐다. Jetpack Compose에서는 구현 방식이 변경되었다.

예시로, Screen A는 ViewModel A를 통해 데이터를 가져와 리스트를 보여주고, Screen B는 각 항목의 세부 정보를 표시한다고 가정해 보겠다. 보통 Hilt를 사용한 ViewModel 구현 및 Jetpack Compose에서 네비게이션을 설정할 때는 다음과 같은 방식을 따른다.

일반적인 구현 방식

Screen A:

@Composable
fun ScreenA(
    navigateToScreenB: () -> Unit,
    viewModel: AViewModel = hiltViewModel()
) {
    // ...
}

@HiltViewModel
class AViewModel @Inject constructor(
    // ...
) : ViewModel() {
    // ...
}

Screen B:

@Composable
fun ScreenB(
    viewModel: AViewModel = hiltViewModel()
) {
    // ...
}

MainActivity에서 네비게이션 설정:

composable("ScreenA") {
    ScreenA(
        navigateToScreenB = {
            navController.navigate("ScreenB")
        }
    )
}
composable("ScreenB") {
    ScreenB()
}

문제점

위 방식은 일반적인 경우에는 문제가 없지만, 공유 ViewModel을 사용하고자 할 때는 문제가 발생한다. 왜냐하면 Screen B로 이동할 때 AViewModel의 새로운 인스턴스가 생성되며, 이로 인해 AViewModel의 init 블록이 다시 실행되기 때문이다.
이는 불필요한 리소스 소모를 초래할 수 있다.

해결 방법

Jetpack Compose에서는 navBackStackEntry를 활용해 네비게이션 스택이 ViewModel의 상태를 인지하도록 설정하여 불필요한 재생성을 방지할 수 있다. 이를 통해 네비게이션 스택에 이미 ViewModel이 존재함을 알리고, 기존의 ViewModel을 재사용하도록 만들 수 있다.


수정된 구현 방식

Screen A:

@Composable
fun ScreenA(
    navigateToScreenB: () -> Unit,
    viewModel: AViewModel
) {
    // ...
}

@HiltViewModel
class AViewModel @Inject constructor(
    // ...
) : ViewModel() {
    // ...
}

Screen B:

@Composable
fun ScreenB(
    viewModel: AViewModel
) {
    // ...
}

MainActivity에서 네비게이션 설정:

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
    )
}

1. composable("ScreenA")

  • composable("ScreenA"): NavHost에 등록된 경로 "ScreenA"에 해당하는 화면을 정의한다.
  • hiltViewModel(backStackEntry): 이 함수는 backStackEntry를 기반으로 AViewModel의 인스턴스를 생성하거나 기존 인스턴스를 반환한다.
    • 여기서 backStackEntryScreenA의 네비게이션 상태를 나타내며, 이를 통해 ViewModel의 재사용이 가능해진다.
    • 결과: AViewModel이 생성되고, ScreenA에서 이를 활용해 데이터를 관리한다.
  • ScreenA: ScreenA Composable은 생성된 AViewModel을 전달받아 UI를 렌더링한다.
  • navigateToScreenB: 클릭 시 navController.navigate("ScreenB")를 호출하여 ScreenB로 이동한다.

2. composable("ScreenB")

  • composable("ScreenB"): NavHost에 등록된 경로 "ScreenB"에 해당하는 화면을 정의한다.
  • if (navController.previousBackStackEntry != null):
    • previousBackStackEntry: NavController의 이전 백스택 항목을 나타낸다.
      이는 "ScreenA"에서의 backStackEntry를 의미한다.
    • 조건의 목적: ScreenB에서 AViewModel을 공유하려면 이전 백스택 항목의 ViewModel을 가져와야 한다.
      • previousBackStackEntry가 존재할 경우: ScreenAbackStackEntry를 활용해 기존의 AViewModel을 가져온다.
      • 존재하지 않을 경우: 새로운 AViewModel을 생성한다.
  • ScreenB: ScreenB Composable은 전달받은 AViewModel을 사용해 UI를 렌더링한다.

핵심

  1. hiltViewModel(backStackEntry): 특정 backStackEntry를 기반으로 ViewModel을 생성하거나 반환한다.
    • ScreenA에서 생성된 ViewModel을 ScreenB에서도 공유하도록 설정.
  2. navController.previousBackStackEntry:
    • ScreenB로 이동했을 때, 이전 화면(ScreenA)의 backStackEntry를 참조해 기존 ViewModel을 재사용한다.
  3. 공유 ViewModel의 장점:
    • AViewModelScreenAScreenB에서 공유하여 API 호출이나 초기화 작업을 반복하지 않아도 된다.
    • 네비게이션 스택을 활용해 ViewModel 인스턴스를 효율적으로 관리할 수 있다.

코드의 실행 흐름

  1. 사용자가 ScreenA를 탐색하면 AViewModel이 생성된다.
  2. ScreenB로 이동 시, AViewModel의 기존 인스턴스를 ScreenA에서 가져와 재사용한다.
  3. 결과적으로 두 화면에서 동일한 ViewModel 인스턴스를 공유하며 데이터 일관성을 유지할 수 있다.

https://medium.com/@mousaieparniyan/shared-viewmodel-in-jetpack-compose-1328f5c895c5

profile
화려한 외면이 아닌 단단한 내면

0개의 댓글

관련 채용 정보