
Compose 화면 전환인 Navigation3에 대해 학습힌다.
화면 전환
1. Compose 화면 전환은 ?
컴포즈는 뷰 계층이 아닌 컴포저블 함수 기반의 UI 트리를 사용하기 때문에 View와는 다른 방식으로 화면 전환, 스택을 관리를 해주는 과정이 필요하다.
상태 기반 UI이기 때문에 단순히 상태 변경으로 화면 전화를 처리할 수 있지만 화면 페이지 증가하면 백스택 관리 어려움, 데이터 전달 불편 같은 문제가 발생하기 때문에 일반적으로 네비게이션 라이브러리를 같이 사용하게 된다.
하지만 Navigation2는 개발자가 직접 백스택 관리를 해야됐기 때문에 사용에 불편함이 컸다. 이런 문제를 해결하기 위해서 Navigation3가 출시되었고 Compose 친화적으로 설계 되었기 때문에 백스택 제어가 가능해 페이지 이동이 편리하고 간단해졌다.
Navigation3
1. Navigation3란 ?
- 사용자가 앱에서 이동하는 각 콘텐츠를 고유한 키로 정의하고 키를 이용해 콘텐츠를 확인한다.
- 각 고유한 키를 이용해 스택 관리가 가능하다.
- NavDisplay를 사용해 스택 변경, 애니메이션 효과를 손 쉽게 적용 가능하다.
키 값을 이용해 화면 스택을 관리, 유지해 주는 편리한 라이브러리다.
2. 탐색과 백스택이란 ?
탐색은 사용자가 앱 내에서 페이지를 이동하는 방식을 나타낸다. 일반적으로는 UI 요소를 탭하거나 클릭 같은 상호 작용을 통해 새 콘텐츠를 표시하고, 뒤로 버튼을 통해 이전 콘텐츠로 이동 같은 동작을 수행한다.

일반적으로 위의 그림과 같이 새로운 스택을 제일 위에 쌓고 제거하며 이전 콘텐츠로 돌아 갈 수 있도록 나타내는 것을 백 스택이라고 한다.
3. Navigation3에서 Back Stack이란?
Nav3에서는 실제로 뒤에 콘텐츠 인스턴스를 가지고 있지 않고 고유한 key를 가지게 된다. 여기서 key는 일반적으로 직렬화가 가능한 데이터 클래스로 이루어진다. 콘텐츠 대신 참조를 사용하게 되는 경우 키를 백 스택에 푸시해 간단하게 탐색이 가능하고 백 스택을 영구 저장소에 저장이 가능하기 때문에 구성 변경 및 프로세스 종료 후에도 유지가 가능하다는 이점이 있다. Nav3는 백스택을 List<T> 타입으로 소유하고 NavDisplay를 사용해 UI에 상태를 반영한다.
// key를 정의한다.
data object ProductList
data class ProductDetail(val id: String)
@Composable
fun MyApp() {
// 시작할 key를 지정하여 백스택을 생성한다.
val backStack = remember { mutableStateListOf<Any>(ProductList) }
// add를 이용해 새로운 key를 스택에 push하여 Nav가 상태를 변경한다.
backStack.add(ProductDetail(id = "ABC"))
// 백스택에서 마지막 key를 pop하여 Nav가 상태를 변경한다.
backStack.removeLastOrNull()
}
4. NavEntry
하나의 페이지를 모델링한 객체를 의미한다. NavEntry 자체가 '화면의 정의'라고 할 수 있다. 하드/저수준 방식으로 키를 이용해 판별하고 NavEntry 인스턴스를 직접 생성해서 반환 시킨다. 런타임에 키 타입을 체크하거나, 복잡한 논리를 넣기 좋다는 장점이 있지만 반복 코드가 생기기 쉽고 타입 캐스팅을 수동으로 해줘야한다는 단점이 있다.
NavEntry는 백스택에 key만 가지고 있고 컴포저블은 실제 화면이 활성화 됐을때 호출/렌더링 되기 때문에 entry-scoped state(ViewModel 등)을 적절히 스코핑해줘야한다.
entryProvider = { key -> when (key) { ... return NavEntry(...) } }
5. entryProvider (DSL)
백스택에 있는 키를 받아 키에 대응하는 NavEntry를 반환해주는 함수이다. NavDisplay 같은 UI 레이어에 전달되어 현재 top key를 NavEntry로 변경해 화면을 렌더링 한다. 고수준/타입 안전 방식으로 entry<T>로 타입을 구별하고 핸들러를 등록해 주기 때문에 내부적으로 key에 맞는 NavEntry를 만들어 반환해 준다.
코드가 깔끔하고 key가 자동으로 캐스팅 되기 때문에 타입 안전성이 보장 되고 람다 내부에서 바로 사용이 가능하다는 장점이 있다. 하지만 런타임에 새롭게 key 타입을 추가해야하는 경우 제약이 있어 이럴 경우엔 직접 반환 방식이 더 편리하다.
entryProvider = entryProvider { entry<ProductList> { ... } entry<ProductDetail> { key -> ... } }
6. NavEntry랑 entryProvider ?
키를 이용해 NavEntry로 화면을 렌더링하는 기능을 동일하지만 DSL은 개발 편의성, 가독성, 타입 안정성을 위해 도입된 편의층이다. NavEntry 개념이 하위이고 entryProvider는 그 위에 있는 편의 API로 볼 수 있다.
7. NavDisplay란 ?
백 스택이 변경될 때마다 UI는 새로운 백 스택 상태를 반영해야 한다. Nav3에서는 NavDisplay가 백 스택을 관찰하고 그에 따른 UI를 업데이트를 해준다.
// 스택 데이터
data object Home
data class Product(val id: String)
@Composable
fun NavExample() {
// backStack 관찰하기 위한 state 생성
val backStack = remember { mutableStateListOf<Any>(Home) }
// backStack UI를 업데이트하기 위해 NavDisplay 등록
NavDisplay(
backStack = backStack,
onBack = { backStack.removeLastOrNull() },
entryProvider = { key ->
when (key) {
is Home -> NavEntry(key) {
ContentGreen("Welcome to Nav3") {
Button(onClick = {
backStack.add(Product("123"))
}) {
Text("Click to navigate")
}
}
}
is Product -> NavEntry(key) {
ContentBlue("Product ${key.id} ")
}
else -> NavEntry(Unit) { Text("Unknown route") }
}
}
)
}
8. Nav3 객체 간 데이터 흐름

- navigation events
- 사용자 상호 작용에 따라 키를 백스택에 추가하거나 제거한다.
- 상태 변경 트리거
- 백스택에 이벤트가 발생하면 NavDisplay는 백 스택의 최상위 항목을 key를 이용해 Entry Provider에 요청하여 반환 받은 NavEntry를 레이아웃에 표시한다.
rememberNavBackStack
1. rememberNavBackStack 이란?
Nav3에서 네비게이션 백 스택 구성 변경이나 프로세스 종료 이후에도 자동으로 저장, 복원을 가능하게 해주는 컴포저블 함수이다. rememberNavBackStack으로 생성한 NavBackStack은 컴포즈의 상태로 구현되기 때문에 NavDisplay에 관찰되고, 프로세스 종료 복구까지 살아있도록 설계되어 있다.
- NavKey interface
- NavDisplay에서 탐색 key로 사용 되는 클래스 객체이기 때문에 NavKey 마커 인터페이스가 필수로 필요하다.
- @Serializable 어노테이션
- 키 클래스나 객체에 @Serializable 어노테이션을 추가해 직렬화를 가능하게 해야한다.
2. 동작 방식
- rememberNavBackStack(initialKey)
- mutableStateList/SnapshotStateList로 상태화된 리스트를 만들어 NaveKey 항목의 초기 백스택을 생성한다.
- backStack.add(..)
- 백스택에 새로운 NavKey를 추가해 새로운 화면을 표시한다.
- backStack.removeLastOrNull()
- 백스택에서 가장 마지막 NavKey를 제거해 이전 화면으로 돌아간다.
- NavDisplay
- backStack을 관찰하고 entryProvider 람다를 사용해 백스택의 마지막 NavKey에 해당하는 컴포저블 화면을 표시한다.
🔍 복잡한 흐름일 경우엔 NavKey가 많아질텐데 어떻게 관리하는게 좋을까 ?
이럴 경우엔 피처 단위별로 독립적인 NavDisplay를 각각 생성해서 백스택을 관리해주면 된다. 탭 전환시에 탭별 히스토리 유지가 가능하다. 또, 큰 화면 안에 작은 화면으로 네이게이션 컨테이너가 필요한 경우에도 작은 화면에 별도의 NavDisplay를 생성해준다면 독립적이기 때문에 스택 관리를 편리하게 할 수 있다.
여러 NavDisplay를 생성할땐 하나의 backStack에 하나의 NavDisplay만 연결하도록 주의가 필요하다. 동시에 여러 NavDisplay가 옵저버하면 UI 상태가 꼬이는 문제가 발생한다.
'Android > Compose' 카테고리의 다른 글
| Compose와 @Immutable, @Stable (0) | 2025.11.14 |
|---|---|
| [Compose] 상태관리 (0) | 2025.02.11 |