Navigation 2 vs 3: 코드상 차이점 완벽 정리 (String vs Type-Safe)

2026. 2. 5. 17:58Android/도구 및 라이브러리

728x90
반응형

최근 안드로이드 개발 커뮤니티에서 "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) 방식은:

  1. 컴파일 타임 안전성: 경로 설정이 잘못되면 빌드 자체가 안 되므로 버그를 미리 잡을 수 있습니다.
  2. 리팩토링 용이: 변수명을 바꿔도 IDE가 알아서 연결된 모든 코드를 수정해 줍니다.
  3. 코드 가독성: 불필요한 보일러플레이트 코드가 확 줄어듭니다.

아직 프로젝트가 String 기반의 내비게이션을 쓰고 있다면, 유지보수와 정신 건강을 위해 Type-Safe 방식(Nav 2.8.0+)으로 마이그레이션 하는 것을 강력 추천합니다!

728x90
반응형