2026. 2. 5. 17:58ㆍAndroid/도구 및 라이브러리
최근 안드로이드 개발 커뮤니티에서 "Navigation 3"라는 단어가 종종 들려옵니다.
사실 구글의 공식 라이브러리 버전은 Navigation 2.8.0 이상을 의미하지만,
기존의 String 기반(Navigation 2) 방식에서 Type-Safe 객체 기반(Navigation 3 스타일)으로 패러다임이 완전히 바뀌었기 때문에 편의상 이렇게 부르고 있습니다.
이번 포스팅에서는 기존 방식(Nav 2)과 새로운 방식(Nav 3)이 코드상에서 어떻게 달라졌는지,
왜 새로운 방식을 써야 하는지 직관적인 코드 비교를 통해 알아보겠습니다.
한눈에 보는 차이점
| 비교 항목 | Navigation 2 (기존) | Navigation 3 (Type-Safe / 2.8.0+) |
| Route 정의 | String (URL 형태) | Object / Data Class |
| 데이터 전달 | 문자열 파싱 ("route/{arg}") | Kotlin Serialization (직렬화) |
| 안전성 | 런타임 에러 발생 (오타 위험) | 컴파일 타임 에러 감지 |
| Argument 처리 | navArgument 수동 정의 | Data Class로 자동 추론 |
1. 라이브러리 설정 (Dependencies)
새로운 방식은 객체를 직렬화하여 전달하므로 kotlinx-serialization 설정이 필수입니다.
Navigation 2 (String 기반)
implementation("androidx.navigation:navigation-compose:2.7.x")
Navigation 3 (Type-Safe 기반)
// build.gradle (Module)
plugins {
// Serialization 플러그인 필수
id("org.jetbrains.kotlin.plugin.serialization") version "1.9.x"
}
dependencies {
implementation("androidx.navigation:navigation-compose:2.8.0")
implementation("org.jetbrains.kotlin:kotlinx-serialization-json:1.6.3")
}
2. Route 정의하기
가장 큰 변화입니다. 더 이상 오타 나기 쉬운 문자열 상수를 관리할 필요가 없습니다.
Navigation 2
object Routes {
const val HOME = "home"
// 인자가 많아질수록 URL 문자열 관리가 힘들어짐
const val DETAIL = "detail/{userId}/{userName}"
}
Navigation 3
import kotlinx.serialization.Serializable
@Serializable
object Home
// 클래스 자체가 경로이자 파라미터가 됨
@Serializable
data class Detail(
val userId: Int,
val userName: String
)
3. NavHost & Argument 받기 (핵심!)
기존의 장황했던 arguments 리스트와 NavType 정의가 사라지고 코드가 매우 깔끔해집니다.
Navigation 2
NavHost(navController, startDestination = Routes.HOME) {
composable(Routes.HOME) { HomeScreen() }
composable(
route = Routes.DETAIL,
// 1. 타입을 일일이 지정해야 함
arguments = listOf(
navArgument("userId") { type = NavType.IntType },
navArgument("userName") { type = NavType.StringType }
)
) { backStackEntry ->
// 2. Key 문자열이 틀리면 앱이 죽음
val userId = backStackEntry.arguments?.getInt("userId") ?: 0
val userName = backStackEntry.arguments?.getString("userName") ?: ""
DetailScreen(userId, userName)
}
}
Navigation 3
NavHost(navController, startDestination = Home) {
composable<Home> { HomeScreen() }
// 1. 제네릭으로 클래스만 넘기면 끝
composable<Detail> { backStackEntry ->
// 2. toRoute()로 안전하게 객체 추출
val args = backStackEntry.toRoute<Detail>()
DetailScreen(args.userId, args.userName)
}
}
4. 화면 이동 (Navigate)
Navigation 2
문자열을 직접 조립해야 했습니다. 슬래시(/) 하나만 빼먹어도 오류가 발생합니다.
val id = 100
val name = "Dev"
// 개발자가 직접 문자열 포맷팅을 해야 함
navController.navigate("detail/$id/$name")
Navigation 3
마치 함수를 호출하듯 객체를 생성해서 넘기면 됩니다.
// 타입 체크가 되며 매우 직관적임
navController.navigate(Detail(userId = 100, userName = "Dev"))
마무리: 왜 넘어가야 할까?
Navigation 2 방식은 유연하지만, String 기반이라 **오타(Typos)**에 취약하고, 파라미터 타입 관리가 번거로웠습니다.
반면 Navigation 3 (Type-Safe) 방식은:
- 컴파일 타임 안전성: 경로 설정이 잘못되면 빌드 자체가 안 되므로 버그를 미리 잡을 수 있습니다.
- 리팩토링 용이: 변수명을 바꿔도 IDE가 알아서 연결된 모든 코드를 수정해 줍니다.
- 코드 가독성: 불필요한 보일러플레이트 코드가 확 줄어듭니다.
아직 프로젝트가 String 기반의 내비게이션을 쓰고 있다면, 유지보수와 정신 건강을 위해 Type-Safe 방식(Nav 2.8.0+)으로 마이그레이션 하는 것을 강력 추천합니다!

'Android > 도구 및 라이브러리' 카테고리의 다른 글
| Android RecognitionListener로 음성 인식하기 (0) | 2025.05.26 |
|---|---|
| [삽질기] JDK 17.0.9에서 17.0.11로 올렸더니 Kapt가 말썽? 해결 방법 공유! (1) | 2025.05.23 |
| Gradle dependencies | implementation vs api 차이, 언제 무엇을 써야 할까? (0) | 2025.02.11 |
| 안드로이드 성능 최적화, 왜 경량 스레드(코루틴)를 써야 할까? (0) | 2025.02.08 |
| Android Studio Compose에서 Color 미리보기 (게터 아이콘) (0) | 2025.01.17 |