본문 바로가기
Android Jetpack Compose/Navigation, Route

Navigation 구현 방법 총정리 # Route Jetpack Compose

by Developer88 2023. 5. 27.
반응형

오늘은 Jetpack Compose 에서 구현하는 Navigation 에 대해서 정리해 보도록 하겠습니다.

 

1. Navigation Library

가장 먼저 준비할 것은 navigation 구현을 위해 라이브러리를 implement 하는 것 입니다.

아래의 라이브러리들이 모두 이 글의 예제를 구현하는데 필수적인 것은 아니지만,

navigation과 viewModel, savedState 등을 같이 사용하기에 도움을 주는 라이브러리이므로 포함시켰습니다.

app레벨의 build.gradle에 implement 시켜주면 됩니다.

 

dependencies {
    def nav_version = "2.5.3"
    implementation "androidx.navigation:navigation-compose:$nav_version"

    // Lifecycle utilities for Compose
    def lifecycle_version = "2.6.1"
    implementation "androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version"
    
    // ViewModel utilities for Compose
    implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"
    
    // Saved state module for ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
}

 

2. Navigation 구현

2-1. NavController

Navigation을 구현하기 위해서 가장 중요한 것은 NavController 입니다.

Composable 들의 backstack 을 추적하며,

각 Screen 들의 state을 관리해 주는 역할을 합니다.

이를 생성하기 위해서 아래 코드를 사용하면 됩니다.

 

val navController = rememberNavController()

 

2-2. NavHost

NavHost 는 코드에 의해서 구현되는 Navigation Graph와 NavController 를 연결시켜줍니다.

이렇게 하면, 특정한 destination (route에 의한 도착지점) 의 Composable로 갈 수 있게 되는데요.

 

코드에 의해서 구현되는 Navigation Graph 는 route에 따라서, 각 Screen들로 연결됩니다.

이렇게 route에 의해서 도착되는 지점들을 destination 이라고 부릅니다.

(여기서부터는 특정화면을 Screen보다는, Navigation 관점에서 Destination이라고 부르겠습니다.)

 

 

NavHost 를 생성하기 위해서는,

위에서 생성했던 navController 가 필요합니다.

NavHost 블록 안의 람다함수에서 navigation grpah 를 생성하게 됩니다.

 

다음과 같이 NavHost 에는 composable 함수들을 넣을 수 있으며,

이 함수들의 구조에 따라서, Navigation Graph 가 생성되게 됩니다.

이 함수에 들어가는 필수적인 인자가 바로 route 입니다.

아래에서는 "profile"과 "setting" 이 바로 route가 되게 됩니다.

 

val navController = rememberNavController()
NavHost(navController = navController, startDestination = "profile") {
    composable(route = "profile") { ProfileScreen() }
    composable(route = "setting") { SettingScreen() }
    ...
}

 

 

참고로 여기서 말하는 route라는 것은,

Navigation Graph를 형성하는 composable로 향하는 path를 가르킵니다.

당연하게도 각각의 Screen, Navigation 상의 Destination들은 중복되지 않는 유니크한 값을 필요로 합니다.

 

composable함수의 인자에 route를 넣게되면, 아래 그림과 같은 Navigation Graph 가 형성되게 되는 것 입니다.

 

 

2-2. Fixed Start Destination

composable("라우트") 함수들을 이용해서 Navigation Graph를 생성할 때,

지켜야 하는 규칙들이 있는데요.

그 중 하나가 바로 Fixed Start Destination 입니다.

당연할 수도 있는 것이지만, 시작점과 다시 돌아올 수 있는 끝점이 있어야 한다는 것 입니다.

아래는 안드로이드 공식문서에 나온 예인데요.

"List Screen"이 바로 Fixed Start Destination 입니다.

 

 

 

참고로 로그인화면이나 유저이름 설정화면과 같이,

처음에만 조건부로 들어가는 화면들은 이러한 고정된 시작점(Fixed Start Destination) 이 되면 안됩니다.

 

3. Destination 간 이동하기

NavHost 안에서 composable("라우트")들을 구성함으로서,

Destination Graph를 생성할 수 있고,

유니크한 라우트 값에 따라서 Destination 으로 이동할 수 있는데요.

navigate()함수는 특정 Destination 으로 이동하도록 해 줍니다.

 

아래와 같이 하면, setting 이라고 하는 라우트로 이동하도록 해줍니다.

 

navController.navigate("setting")

 

 

이것을 응용해서, 

아래와 같이 버튼을 누르면,

setting Destination으로 이동할 수 있도록 할 수 있습니다.

 

Button(onClick = {
    navController.navigate("setting")
}) {
    Text(text = "setting 으로 이동하기")
}

 

4. route 이동시 값 전달하기

4-1. route 이동시 값 전달

Destination 이동 시에 값을 전달해야 할 경우도 있는데요.

그럴때는 아래와 같이,

composable 에 다음의 2가지 인자를 넣어줍니다.

  • route
    • "setting/{userId} : userId라는 dynamic한 값 전달
      • 예를 들어, setting/123 으로 route를 넘기면, 123이라는 값을 전달할 수 있게 됩니다.
  • arguments
    • 라우트를 통해 전달받은 값들의 list 
    • navArgument("userId"){ type = NavType.IntType }
      • 여기서 argument 는 userId로 넘기는 값의 이름입니다.
      • type은 넘기는 데이터의 타입을 지정해주는 것 입니다. 여기서는 Int 타입입니다.

 

 

전체 코드를 보면서 좀 더 이해해 보겠습니다.

 

@Composable
fun MyNav() {
    val navController = rememberNavController()
    NavHost(navController, startDestination = "setting/123") {
        composable(
        	"setting/{userId}", 
        	arguments = listOf(navArgument("userId") { 
        		type = NavType.IntType 
            })) { backStackEntry ->
                val userId = backStackEntry.arguments?.getInt("userId")
                SettingScreen(userId = userId)
        }
        composable("home") {
            Button(onClick = { navController.navigate("setting/456") }) {
                Text("Go to Setting")
            }
        }        
    }
}

@Composable
fun SettingScreen(userId: Int?) {
    // Some UI code for setting screen...
    Text("Setting for User: $userId")
}

 

만약 타입에 null이 사용이 가능하다면,

nullable은 true 로 해주고, defaultValue를 null로 해 줄수도 있습니다.

 

composable(
    "setting/{name}", 
    arguments = listOf(
        navArgument("name") { 
            type = NavType.StringType
            nullable = true
            defaultValue = null
        }
    )
) { backStackEntry ->
    val name = backStackEntry.arguments?.getString("name")
    SettingScreen(name = name)
}

 

 

destination들간에 보낼 수 있는 데이터타입들은 다음과 같습니다.

타입들은 여러가지 이지만, 데이터를 주고 받을 때, String으로 주고 받게 됩니다.

 

https://developer.android.com/guide/navigation/use-graph/pass-data#supported_argument_types

 

한가지 주의할 점은 공식문서에 적혀있는 다음 문구부분입니다.

Route에 데이터를 넘길때 String 으로부터 파싱해서 넘겨지기 때문에,

복잡한 형태의 Custom Data를 넘기지 못한다는 것입니다.

당연히 Moshi나 Gson같은 것으로 Serialize 해서 넘길수도 있겠지만, 그러한 것은 권장되지 않구요.

기본적으로는 복잡한 형태를 Destination간에 넘기는 것을 지양하도록 되어 있습니다.

 

 

 

4-2. savedStateHandle 을 이용해서 viewModel 에서 받아오기

위에서 보았던 것과 같이, 값을 해당 Screen 을 호출할 때 넘겨줄수도 있지만,

아래와 같이 viewModel에서 받아올 수도 있습니다.

 

@HiltViewModel
class SettingViewModel @Inject constructor(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    val userId: Int? = savedStateHandle.get<Int>("userId")
}

 

 

이와 관련해서 좀 더 자세한 사항은 아래 글을 참조해 주세요.

>> Route 에서 전달된 값을 ViewModel 에서 받는 방법 # Jetpack Compose Navigaion savedStateHandle

 

5. Nested Navigation

앱을 실제로 만들다 보면 중첩된 형태의 Navigation이 필요할 수 있는데요.

이 때는, Nested Navigation을 구현해서 사용하면 됩니다.

구현 방법이 크게 복잡하지는 않은데요.

이와 관련해서는 아래 글을 참조해 주세요.

>> Nested Navigation 구현방법 # 중첩 Route Jetpack Compose

 

728x90

댓글