오늘은 Android 개발에서 화면 간 이동을 더욱 쉽게 만들어주는 NavController와 NavHost에 대해 알아볼게요!
Jetpack Compose에서는 기존 Android 방식보다 간결하고 유연한 화면 전환을 구현할 수 있는 도구를 제공합니다.
이 글에서는 NavController와 NavHost의 역할, 동작 방식, 기존 방식과의 차이점을 자세히 설명해 드릴게요.
1. NavController란?
NavController는 Jetpack Compose에서 화면 전환의 중심 역할을 담당하는 객체예요.
앱에서 여러 화면을 넘나들 때, NavController는 이동을 처리하고 뒤로 가기 같은 기능도 관리해 줘요.
화면 전환뿐만 아니라, 뒤로 가기 스택 관리 등 복잡한 로직도 자동으로 처리해 줘요.
쉽게 말해, '현재 어느 화면에 있고, 어디로 이동할지'를 결정하는 역할을 하는 거예요.
NavController의 주요 기능
- 화면 전환 제어
- `navigate(route: String)` 메서드를 사용해 특정 화면으로 이동할 수 있어요.
navController.navigate("profile")
- `navigate(route: String)` 메서드를 사용해 특정 화면으로 이동할 수 있어요.
- 뒤로 가기 동작 관리
- `popBackStack()`을 호출하면 현재 화면을 백스택에서 제거하고 이전 화면으로 돌아가요.
navController.popBackStack()
- `popBackStack()`을 호출하면 현재 화면을 백스택에서 제거하고 이전 화면으로 돌아가요.
- 데이터 전달
- 화면 간 데이터를 전달할 때 경로(route)에 매개변수를 포함할 수 있어요.
navController.navigate("detail/42")
- 화면 간 데이터를 전달할 때 경로(route)에 매개변수를 포함할 수 있어요.
- 현재 상태 확인
- 현재 화면의 경로(route)와 같은 정보를 확인할 수 있어요.
val currentDestination = navController.currentBackStackEntry?.destination?.route
- 현재 화면의 경로(route)와 같은 정보를 확인할 수 있어요.
2. NavHost란?
NavHost는 NavController가 관리하는 화면들의 컨테이너 역할을 해요.
쉽게 말하면, 앱에서 사용할 모든 화면을 정의하고, 화면 간의 이동 경로를 설정하는 곳이에요.
NavHost의 주요 구성 요소
- NavController 연결
- NavController를 NavHost에 전달해 화면 전환을 제어해요.
- Start Destination
- 앱이 시작될 때 가장 먼저 보여줄 화면(route)을 지정해야 해요.
- Composable 등록
- 각 화면을 composable() 함수로 정의하고 경로(route)를 연결해요.
- NavController 연결
- NavController를 NavHost에 전달해 화면 전환을 제어해요.
- Start Destination
- 앱이 시작될 때 가장 먼저 보여줄 화면(route)을 지정해야 해요.
- Composable 등록
- 각 화면을 composable() 함수로 정의하고 경로(route)를 연결해요.
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") { HomeScreen() }
composable("detail/{itemId}") { backStackEntry ->
val itemId = backStackEntry.arguments?.getString("itemId")
DetailScreen(itemId)
}
}
3. NavController와 NavHost의 관계
- NavController는 화면 전환의 실행자
- NavHost는 화면 전환의 설계자
NavController는 현재 화면 상태를 관리하고 이동을 실행하며, NavHost는 앱 전체의 화면 구조를 정의하고 관리해요.
두 요소가 협력해 사용자의 화면 이동 경험을 만들어줘요.
4. NavController와 NavHost 사용 예시
간단한 예시로 볼게요. 먼저 NavController를 생성하고, 그다음에 NavHost를 설정하는 흐름이에요.
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(onNavigate = {
navController.navigate("details")
})
}
composable("details") {
DetailsScreen()
}
}
코드 설명
- `rememberNavController()`: NavController를 생성합니다.
- `NavHost()`: NavController와 연결된 네비게이션 호스트를 생성하고, 시작 화면을 **"home"**으로 설정해요.
- 경로(route) 정의
- `composable("home")`: 홈 화면을 정의하고, 이동 버튼을 눌렀을 때 "details" 화면으로 이동하도록 `navController.navigate("details")`로 설정했어요.
- `composable("details")`: 상세 화면을 정의했어요.
NavGraphBuilder와 composable
위 코드에서 `NavHost` 내에 `composable`이라는 함수가 보이죠?
이 함수는 각각의 화면(route)을 정의해 주는 역할을 해요.
`NavGraphBuilder`의 `composable` 함수는 우리가 만든 화면을 네비게이션 그래프에 추가할 때 사용해요. 여기서 "home"과 "details"는 각각의 화면을 구분하기 위한 고유한 경로(route)라고 보면 돼요.
이렇게 정의된 경로를 통해 NavController가 각 화면을 관리하고 이동할 수 있게 돼요.
5. NavOptions로 네비게이션 제어하기
NavController는 화면 이동 시 다양한 옵션을 설정할 수 있어요.
이를 통해 특정 화면으로 이동할 때 애니메이션 효과를 추가하거나, 스택에서 화면을 제거하는 등의 작업을 할 수 있어요.
navController.navigate("details") {
popUpTo("home") { inclusive = true }
launchSingleTop = true
}
주요 옵션
- `popUpTo()`
- 특정 화면까지 Back Stack에서 제거해요.
- `inclusive = true`로 설정하면 해당 화면도 함께 제거돼요.
- `launchSingleTop`
- 현재 화면이 이미 백스택에 있는 경우, 새로운 인스턴스를 생성하지 않고 기존 화면을 재사용해요.
6. 기존 Android Navigation 방식과의 차이점
Compose Navigation은 기존 Android Navigation Component와 공통점을 가지고 있지만, 선언형 프로그래밍에 맞춰 설계된 덕분에 여러 차이점이 있어요. 아래 표에서 두 방식을 비교한 뒤, 자세한 설명을 이어갈게요.
특징 | 기존 Android | Compose |
네비게이션 정의 | XML에서 네비게이션 그래프 작성 | Kotlin 코드 기반으로 네비게이션 그래프 작성 |
화면 단위 | Fragment 또는 Activity | Composable |
데이터 전달 | Safe Args 플러그인 필요 | Route + Arguments |
상태 관리 | ViewModel/Bundle 필요 | State Hoisting 가능 |
애니메이션 처리 | XML 기반 Transition | 코드로 애니메이션 설정 가능 |
뷰 계층 | Fragment + Activity 계층 필요 | 단일 Activity에서도 모든 화면 전환 처리 가능 |
- 네비게이션 정의 방식
- 기존 Android: XML 파일에서 네비게이션 그래프를 작성했어요.
Action ID를 사용해 화면 전환 경로를 정의했으며, XML 파일과 Kotlin 코드를 분리해 관리해야 했죠.<navigation> <fragment android:id="@+id/homeFragment" /> <fragment android:id="@+id/detailFragment" /> </navigation>
- Compose: XML 대신 Kotlin 코드로 모든 화면과 경로를 정의해요.
UI와 네비게이션 로직을 같은 파일에서 관리할 수 있어 유지보수가 더 쉬워졌어요.NavHost(navController = navController, startDestination = "home") { composable("home") { HomeScreen() } composable("detail/{itemId}") { DetailScreen() } }
- 기존 Android: XML 파일에서 네비게이션 그래프를 작성했어요.
- 화면 단위
- 기존 Android: Fragment 또는 Activity로 화면을 구성했어요.
Fragment는 강력한 기능을 제공하지만, 생명주기 관리와 트랜잭션 처리 등이 복잡했죠. - Compose: 화면을 가볍고 단순한 Composable 함수로 작성해요.
선언형 프로그래밍 특성 덕분에 생명주기를 관리하지 않아도 되고, UI 업데이트도 간단히 처리할 수 있어요.
- 기존 Android: Fragment 또는 Activity로 화면을 구성했어요.
- 데이터 전달 방식
- 기존 Android: Safe Args 플러그인을 사용해 데이터를 전달했어요.
Safe Args는 타입 안전성을 보장하지만, 별도의 설정이 필요했어요. - Compose: Safe Args 없이 경로(route)에 매개변수를 포함하면 데이터를 간단히 전달할 수 있어요.
composable("detail/{itemId}") { backStackEntry -> val itemId = backStackEntry.arguments?.getString("itemId") DetailScreen(itemId) }
- 기존 Android: Safe Args 플러그인을 사용해 데이터를 전달했어요.
- 상태 관리 방식
- 기존 Android: Fragment에서 상태를 유지하려면 ViewModel 또는 `onSaveInstanceState`를 사용해야 했어요.
이는 화면 복구 시 복잡한 코드를 작성해야 하는 번거로움이 있었죠. - Compose: 상태를 State Hoisting을 통해 자연스럽게 관리할 수 있어요.
Compose는 선언형 UI와 상태 관리가 밀접하게 통합되어 상태 복구가 더 간단해졌어요.
- 기존 Android: Fragment에서 상태를 유지하려면 ViewModel 또는 `onSaveInstanceState`를 사용해야 했어요.
- 애니메이션 처리
- 기존 Android: 화면 전환 애니메이션을 XML Transition 파일에서 정의했어요.
애니메이션을 적용하려면 추가적으로 코드에서 명시적으로 호출해야 했죠. - Compose: 애니메이션을 코드로 정의하고 유연하게 커스터마이징 할 수 있어요.
composable( route = "detail", enterTransition = { fadeIn() }, exitTransition = { fadeOut() } ) { DetailScreen() }
- 기존 Android: 화면 전환 애니메이션을 XML Transition 파일에서 정의했어요.
- 뷰 계층 간소화
- 기존 Android: Fragment와 Activity 계층이 중첩되어 복잡한 네비게이션 구조를 초래했어요.
- Compose: 단일 Activity에서도 모든 화면 전환을 처리할 수 있어 뷰 계층이 훨씬 단순해졌어요.
7. Compose Navigation의 장점
- 구조 간소화
- Fragment 트랜잭션과 XML 설정 없이 화면 전환을 처리할 수 있어요.
- 유지보수성 향상
- 네비게이션 그래프와 UI 코드가 한 곳에 있어 관리가 더 쉬워졌어요.
- 더 적은 Boilerplate 코드
- Safe Args, XML 리소스 설정, Fragment 트랜잭션 코드가 필요 없어요.
- 유연한 애니메이션 처리
- 애니메이션을 코드로 정의하고 자유롭게 커스터마이징할 수 있어요.
- 데이터 전달의 간편함
- 경로(route)를 통해 화면 간 데이터를 쉽게 전달할 수 있어요.
참고자료
https://developer.android.com/develop/ui/compose/navigation?hl=ko
'Android > Compose' 카테고리의 다른 글
[Android] Stateful, Stateless 컴포저블 차이와 상태 호이스팅 (1) | 2024.09.04 |
---|---|
[Android] Modifier 전달과 생성의 차이 (0) | 2024.09.04 |
[Android] Jetpack Compose에서 Scaffold와 Surface의 차이점 (1) | 2024.09.03 |