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

Nested Navigation 구현 및 총정리 # 중첩 Route Jetpack Compose

by Developer88 2023. 5. 26.
반응형

실제 앱을 만들다보면, Navigation Graph가 복잡해 지게 됩니다.

중첩된, 즉 nested navigation이 필요하기 때문인데요.

오늘은 Jetpack Compose 에서 Nested Navigation 을 구현하는 방법에 대해서 정리해 보겠습니다.

 

이 글을 이해하기 위해서는 Jetpack Compose Navigation 구현방법에 대해서 알고 있어야 하는데요.

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

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

 

1. 라이브러리 Implementation

이 글에 나오는 코드를 구현하기 위해서,

아래 라이브러리들이 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"
}

 

2. 글의 목표

오늘 글의 목표는 아래와 같은 간단한 구조를 Nested Navigation으로 구현하는 것입니다.

 

 

nested가 필요하다고 하기에는 너무나 간단한 구조이지만,

NavHost에서 아이템리스트(list)와 설정(setting)으로 이동하는 Navigation과,

아이템리스트내부에서 아이템편집 화면으로 이동하는 Navigation이 중첩되어 있습니다.

 

3. NavGraphBuilder.navigation() 확장함수

nested 된 Navigation 그래프를 구현하기 위해서 필요한 API가,

NavGraphBuilder의 확장함수인 NavGraphBuilder.navigation() 함수입니다.

이 함수를 구현하기 위해서는, 아래의 2개의 인자가 필요합니다.

  • startDestination: nested NavGraph에서 처음시작화면의 route
  • route: 해당 destination의 유니크한 route 이름

 

 

 

이제 이 navigation() API를 이용해서 어떻게 구현하는지만 알면 되겠지요.

 

3. 구현

3-1. navigation()함수 구현

'1. 글의 목표'에서 보았던 구조로 nested Navigation을 구현하면 다음과 같은데요.

NavHost 블록 안에서는 NavGraphBuilder의 navigation()을 아래와 같이 바로 호출해서 사용해 주면 됩니다.

 

아래에서는 이 nestedNavGraph의 route는 item_screen으로 하였고요.

그 안에 list와 edit 중 startDestination은 list로 하였습니다.

 

NavHost(navController, startDestination = "list") {
    navigation(startDestination = "list", route = "item_graph") {
        composable("list") { List() }
        composable("edit") { Edit() }
    }
    composable("setting"){ Setting() }
}

 

한 가지 알아둘 점은, 이 nestedGraph의 route명은 item_screen이지만, startDestination이 list이므로,

자동으로 list 페이지로 이동하게 되어서 현재의 route에서 item_screen을 볼일은 없을 것입니다.

 

 

3-2. navigation()모듈화

앱을 만들다 보면, 구조가 복잡해지고 Navigation도 많아지므로,

코드를 분리해서 모듈화해주면 주면 좋은데요.

 

이를 위해서는, 아래와 같이 NavGraphBuilder의 확장함수형태로 만들어 줍니다.

당연히 확장함수명은 아래코드를 따르지 않고, 상황에 맞게 만들어주면 됩니다.

 

fun NavGraphBuilder.nestedItemGraph(navController: NavController){
    navigation(startDestination = "list", route = "item_graph") {
        composable("list") { List() }
        composable("edit") { Edit() }
    }
}

 

route는 이 nestedItemGraph의 고유한 이름입니다.

따라서 startDestination과 같은 라우트를 사용하면 안됩니다.

이럴 경우, IllegalArgumentException을 만나게 됩니다.

위에서 이 전체 nestedRoute에 대한 명칭은 "item_screen" 으로 잡았고,

그 안의 시작점인 list에 대해서는 "list"로 한 이유입니다.

 

이제 아래와 같이 호출해서 사용해주기만 하면 됩니다.

 

NavHost(navController, startDestination = "list") {
    nestedItemGraph(navController)
    composable("setting"){ Setting() }
}

 

4. When

위에서 Nested Navigation 의 구현에 대해서 알아보았는데요.

언제 Nested Navigation을 구현해야 하는 것 인지 생각해 보겠습니다.

 

B에서 C와 D로 라우팅이 분기된다고 무조건 Nested Navigation으로 구현하는 것이 맞을까요?

 

A -> B -> C
       -> D

 

 

Nested Navigation 을 이용하면,

하나의 root가 되는 destination을 가지고 그룹화가 되고, 

위에서 언급하였던 모듈화의 장점들이 생기게 되는데요.

페이지가 많고, UX 플로우가 복잡해질수록 의미가 있게 됩니다.

 

반대로, 이러한 장점들이 필요하지 않은 단순한 페이지들의 경우는,

복잡하게 Nested Navigation으로 구현할 필요는 없습니다.

Jetpack Compose의 Navigation은,

B, C, D를 Nested Navigation으로 구현하지 않고,

중첩되지 않은 top-level destination 으로 취급해서 작업해도 이상없이 동작하도록 되어있습니다.

즉, 논리적으로 분기가 되기 때문에 Nested로 구현해야 되는 것이 아니라는 것 이지요.

 

따라서, 구현하려는 페이지가 Nested Navigation으로 구현해야 하는 페이지인지,

아니면 단순하게 분기만 한번 나누어 지고,

페이지간 연결성도 없고 서브페이지도 없어서,

Nested Navigation 으로 구현할 필요가 없는 것인지 생각해 보고 구현을 하는 것이 효율적 입니다.

 

5. BackStackEntry 와 SharedViewModel

5-1. BackStackEntry

Jetpack Compose Navigation 에서는,

Navigation graph 들은 부모로부터 독립된 각각의 BackstackEntry를 가지게 됩니다.

BackstackEntry는 단순히 Desination들을 push하고 pop 해서 꺼내는 역할 뿐만이 아니라,

destination의 state 정보도 가지고 있는 중요한 장소인데요.

 

NestedNavigation 은 속해있는 route들이 하나의 통합된 BackStackEntry를 가지게 됩니다.

이들이 ViewModel 을 공유하여서 login에 관련된 State들을 통합적으로 관리하면 더욱 효율적이겠지요.

nestedNavigatoin들간에 ViewModel을 공유하는 것에 대해서도 알아보겠습니다.

 

5-2. ViewModel 공유하기

아래와 같이 hilt를 사용해 생성하는 ViewModel 이 있다고 가정해 보겠습니다.

 

@HiltViewModel
class SharedViewModel @Inject constructor() : ViewModel() {
    ...
}

 

그럼 이것을 아래와 같이 공유해서 사용하는 것이 가능해 집니다.

참고로 아래 코드는 다음의 구조로, login 부분을 Nested Navigation으로 구현하고 있습니다.

  • home
  • login(Nested Navigation)
    • username,
    • password,
    • registration

 

@Composable
fun MyNav() {
    val navController = rememberNavController()

    NavHost(navController, startDestination = "home") {
        navigation(startDestination = "username", route = "login_graph") {
           composable("username") {
                Button(onClick = { navController.navigate("password") }) {
                    Text("Go to Password")
                }
            }

            composable("password") {
                Button(onClick = { navController.navigate("registration") }) {
                    Text("Go to Registration")
                }
            }

            composable("registration") {
                Button(onClick = { navController.navigate("home") }) {
                    Text("Go back to Home")
                }
            }            
        }
        composable("home") { ... }
    }
}

@Composable
fun UsernameScreen() {
    val sharedViewModel: SharedViewModel = hiltViewModel()
    ...
}

@Composable
fun PasswordScreen() {
    val sharedViewModel: SharedViewModel = hiltViewModel()
    ...
}

@Composable
fun RegistrationScreen() {
    val sharedViewModel: SharedViewModel = hiltViewModel()
    ...
}

 

 

하나의 BackStackEntry에서 관리되어서, State들을 통합적으로 관리할 수 있고,

ViewModel도 해당 nested navigation들이 pop되는 것과 동시에 사라지므로,

깔끔하게 관리된다고 할 수 있겠습니다.

 

이 login nested navigation의 마지막에는,

아래코드와 같이, popUpTo()를 이용해서 login에 해당하는 Stack 을 한번에 pop 시켜주면 되겠습니다.

더이상 필요하지 않은 login관련 route 들을 다 pop시키고, home route로 이동시켜 줄 수 있습니다.

 

Button(onClick = {
    navController.navigate("home") {
        popUpTo("login_graph") { inclusive = true } 
    }
}) {
    Text("로그인 후 홈으로")
}

 

이상으로 Nested Navigation 대해서 정리해 보았습니다.

728x90

댓글