[TroubleShooting] ViewModel 공유하기

Ted_Moon99·2025년 1월 20일
0

Trouble Shooting

목록 보기
2/2
post-thumbnail

오늘은 회원가입을 개발하는 중이었는데 기존에 설계된 ViewModel 및 화면 전환에 있어서 문제점을 발견하였다.

요구사항

회원가입에 필요한 화면이 ScreenA, ScreenB, ScreenC가 있다고 가정하자.

  • ScreenA : 이메일비밀번호 입력
  • ScreenB : 이름전화번호 입력
  • ScreenC : 환영 페이지

화면 플로우는 다음과 같다.

ScreenA -> ScreenB(회원가입 요청) -> ScreenC

현재 상황

ViewModel

class SignUpViewModel @Inject constructor(
	private val repository: SignUpRepository
): ViewModel(){
	
}

ScreenA

@Composable
fun ScreenA(
	// ''' 
	viewModel: SignUpViewModel = hiltViewModel(),
    // ''' 
) 

ScreenB

@Composable
fun ScreenB(
	// ''' 
	viewModel: SignUpViewModel = hiltViewModel(),
    // ''' 
) 

AppNavHost

val navController = rememberNavController()

composable("ScreenA"){
	ScreenA(
    	// '''
        onNextButtonClicked = {
        	navController.navigate("ScreenB")
        },
        // '''
    )
}

composable("ScreenB"){
	ScreenB(
    	// '''
        onNextButtonClicked = {
        	navController.navigate("ScreenC")
        },
        // '''
    )
}

문제점

이러한 방식으로 그동안 많이 썼었는데 이번에 ScreenA와 ScreenB가 데이터를 공유해야 하는 과정에서 한계를 직면했다. 이 방식에는 문제가 존재한다.

위의 방식의 경우
ScreenA에서 ScreenB로 이동할 때 Hilt를 사용하더라도 ViewModel 인스턴스가 새로 생성된다.

  1. 각 화면은 자신의 고유한 NavBackStackEntry를 가진다.
  2. navController.navigatie()로 새로운 화면을 추가하면, 새로운 NavBackStackEntry가 생성된다. 이때 해당 엔트리에 맞는 새로운 ViewModel이 생성된다.
  3. hiltViewModel()은 현재 활성화된 NavBackStackEntry와 연결된 ViewModel을 제공한다.

위와 같은 이유 때문에 ScreenA에서 저장했던 데이터가 ScreenB에서 ViewModel을 통해 조회할 경우 초기값으로 나오는 문제가 발생하였다.

위와 같은 사유로 서버에 데이터를 전송할 때 BAD REQUEST인 HTTP 400 에러가 발생하였다.

해결방법

Jetpack Compose에서는 navBackStackEntry를 활용해 네비게이션 스택이 ViewModel의 상태를 인지하도록 설정하여 불필요한 재생성을 방지할 수 있다.

ScreenA

@Composable
fun ScreenA(
	// '''
	viewModel: SignUpViewModel,
    // '''
) { 
	// ''' 
}

ScreenB

@Composable
fun ScreenB(
	// '''
	viewModel: SignUpViewModel,
    // '''
) { 
	// ''' 
}

ViewModel

@HiltViewModel
class SignUpViewModel @Inject constructor(
	// '''
): ViewModel() {
	// '''
}

AppNavHost

composable("ScreenA"){ backStackEntry: NavBackStackEntry ->
	val viewModel: SignUpViewModel = hiltViewModel(viewModelStoreOwner = backStackEntry)
    
    ScreenA(
    	// '''
    	viewModel = viewModel,
        onNextButtonClicked = {
        	navController.navigate("ScreenB")
        },
        // ''' 
    )
}

val viewModel: SignUpViewModel = hiltViewModel(viewModelStoreOwner = backStackEntry)는 NavBackStackEntry를 기반으로 SignUpViewModel의 인스턴스를 새롭게 생성하거나 기존 인스턴스를 반환한다

composable("ScreenB"){ backStackEntry: NavBackStackEntry ->
	val viewModel: SignUpViewModel = if (navController.previousBackStackEntry != null){
                // 이전 화면이 존재하는 경우
                hiltViewModel(viewModelStoreOwner = navController.previousBackStackEntry!!)
            } else {
                // 이전 화면이 존재하지 않는 경우
                hiltViewModel()
            }
        
    ScreenB(
    	// '''
    	viewModel = viewModel,
        onNextButtonClicked = {
        	navController.navigate("ScreenC")
        },
        // ''' 
    )
}

이제 정상적으로 동작하는지 다시 로그를 찍어 확인해보겠다.

드디어 두 화면간에 데이터가 공유되어 정상적으로 API 호출이 이루어진 것이 확인된다.

핵심정리

  1. hiltViewModel(backStackEntry: NavBackStackEntry) : 특정 NavBackStackEntry를 기반으로 ViewModel을 생성하거나 반환한다

  2. 공유 ViewModel의 장점:

    • ViewModel을 여러 화면에서 공유할 수 있으므로 API 호출 / 초기화 작업을 반복하지 않아도 된다.
    • 내비게이션 스택을 활용해 ViewModel의 인스턴스를 효율적으로 관리할 수 있다
    • ViewModel을 공유하므로 이전 화면에서 ViewModel에 저장해둔 데이터를 사용할 수 있다.

Reference

ViewModel 공유하기

profile
서버 및 모바일 앱 개발자

0개의 댓글

관련 채용 정보