
현재 스프린트를 진행중인 스매싱 프로젝트에서 navigation 리팩 작업을 하면서 저도 헷갈리는 것들이 있고, 앞으로 화면 라우팅을 세팅할때 어떻게 하면 좋을 지 고민해 본 내용을 적어보았습니다.
기존 메인 탭 화면의 경우는 아래와 같았습니다.
val navOptions = navOptions {
navController.currentDestination?.route?.let {
popUpTo(it) {
saveState = true
inclusive = true
}
launchSingleTop = true
restoreState = true
}
}
when (tab) {
MainTab.HOME -> navController.navigateToHome(navOptions = navOptions)
MainTab.SEARCH -> navController.navigateToSearch(navOptions = navOptions)
MainTab.MATCHING -> navController.navigateToMatching(navOptions = navOptions)
MainTab.PROFILE -> navController.navigateToMyProfile(navOptions = navOptions)
}
navOptions는 navigate() 호출 시 네비게이션 동작을 제어하기 위한 옵션 모음입니다.
(이동하면서 백스택을 어떻게 다룰지 / 기존 상태를 어떻게 처리할지를 결정)
popUpTo로 지정한 목적지까지 포함해서 제거할지 여부popUpTo로 인해 제거되는 목적지의 SavedState 기반 상태를 저장합니다. 예를 들어 home → search로 이동할 때, home 화면의 rememberSaveable 상태나 스크롤 위치, viewmodel 등 복원 가능한 UI 상태가 저장될 수 있습니다.saveState = true로 저장해둔 상태가 있다면, navigate() 시점에 이를 복원합니다.saveState와 함께 사용될 때 의미가 있으며, 저장된 상태가 없으면 복원되지 않습니다.위 내용을 통해, 정리하자면 기존 메인 탭의 경우는 탭 간 이동시 아래와 같이 동작합니다.
inclusive = true 로 항상 새로 생성되기 때문에 스택에 존재하는 라우트가 없음. 현재로서는 큰 의미 없음saveState로 저장된 상태가 있다면 이를 복구여기서 주의 깊게 봐야할 부분은 saveStae 와 restoreState 부분 입니다.
해당 값으로 인해 NavBackStackEntry 는 복구되고 viewmodel 은 다시 복구 됩니다.
(viewmodel 은 ViewmodelStore 를 key 처럼 사용해서 기존에 값이 있으면 복구하고, NavBackStackEntry 는 ViewmodelStore 를 가지고 있는 ViewModelStoreOwner 를 상속 받고 있습니다.)
viewmodel 복구 방식 참고: https://yangsooplus.tistory.com/8
상태가 유지되는 방식(특히 스크롤 위치 유지)은 UX 관점에서는 장점이 될 수 있지만, 구현 방식에 따라 의도치 않은 사이드 이펙트를 만들 수 있습니다.
대표적으로 Home에서 Search 화면으로 이동한 뒤 스크롤을 내렸다가, 다시 탭 이동/복귀를 반복하는 경우:
LaunchedEffect 기반 fetch)이 무한 스크롤 조건과 결합되면 연쇄적으로 fetch가 발생하게 됩니다.정리하자면, 기존에 저희는 LaunchedEffect로 화면이 구성될 때마다 fetch 하는 로직을 넣어두었습니다.
이로 인해, 탭 이동/복귀 과정에서 UI 상태가 유지된 채로 재구성되는 상황이 생기다 보니, 무한 스크롤 조건과 결합되면 연쇄적으로 fetch하는 케이스가 발생했습니다.
savedStateHandle 로 refresh 트리거 하기
이전 화면에서 Search를 새로고침 해야 하는 상황이 명확한 경우, SavedStateHandle로 refresh 플래그를 전달하고 Search 쪽에서 이를 받아 refresh()를 수행하는 방법입니다.
상태 값을 기준으로 fetch 트리거를 제한하기
Search에서 fetch가 필요해지는 조건이 실제로는 아래처럼 정해져 있었습니다.
이 중에서 지역/티어/성별 변경은 검색 조건이 바뀐 것이기 때문에,
해당 값이 변경될 때는 아래를 함께 수행하도록 했습니다.
반대로 바텀탭 이동/복귀는 조건이 바뀐 것이 아니라 화면을 다시 보는 것이므로, 탭 복귀 시에는 스크롤 상태를 그대로 유지하고 fetch를 실행하지 않는 방향을 선택할 수 있습니다.
결론적으로 제가 선택한 방식은 바텀탭 클릭 시마다 자동으로 fetch를 수행하기보다는,
이 방식이 네트워크 호출을 예측 가능하게 만들고, 상태 복원의 장점도 살릴 수 있다고 판단했습니다.
PS. 최근 Navigation3와 같은 새로운 네비게이션 구조가 등장했지만, 이번에는 기존 Navigation 구조를 충분히 이해하고 문제를 해결하는 데 집중했습니다.
추후 안정화 시점에 맞춰 Navigation3 도입을 고려해 리팩토링할 예정입니다 :)