탐색 그래프 설계

탐색 구성요소탐색 그래프를 사용하여 앱의 탐색을 관리합니다. 탐색 그래프는 앱 내의 각 대상과 대상 간의 연결을 포함하는 데이터 구조입니다.

대상 유형

일반적으로 대상에는 호스팅된 대상, 대화상자 대상, 활동 대상이라는 세 가지 유형이 있습니다. 다음 표에는 이러한 세 가지 대상 유형과 그 목적이 요약되어 있습니다.

유형

설명

사용 사례

호스팅된 대상

전체 탐색 호스트를 채웁니다. 즉, 호스팅된 대상의 크기가 탐색 호스트의 크기와 동일하며 이전 대상은 표시되지 않습니다.

기본 및 세부정보 화면

대화상자 대상

오버레이 UI 구성요소를 표시합니다. 이 UI는 탐색 호스트의 위치 또는 크기와 연결되지 않습니다. 이전 대상은 대상 아래에 표시됩니다.

알림, 선택, 양식

활동 대상

앱 내의 고유 화면 또는 기능을 나타냅니다.

탐색 구성요소와 별도로 관리되는 새 Android 활동을 시작하는 탐색 그래프의 종료 지점 역할을 합니다.

최신 Android 개발에서 앱은 단일 활동으로 구성됩니다. 따라서 활동 대상은 서드 파티 활동과 상호작용할 때 또는 이전 프로세스의 일부로 사용하는 것이 가장 좋습니다.

이 문서에는 가장 일반적이고 기본적인 대상인 호스팅된 대상의 예가 포함되어 있습니다. 다른 대상에 관한 자세한 내용은 다음 가이드를 참고하세요.

프레임워크

모든 경우에 동일한 일반 워크플로가 적용되지만 탐색 호스트와 그래프를 만드는 정확한 방법은 사용하는 UI 프레임워크에 따라 다릅니다.

  • Compose: NavHost 컴포저블을 사용합니다. Kotlin DSL을 사용하여 NavGraph를 추가합니다. 다음 두 가지 방법으로 그래프를 만들 수 있습니다.
    • NavHost의 일부로: NavHost를 추가하는 과정에서 탐색 그래프를 직접 구성합니다.
    • 프로그래매틱 방식: NavController.createGraph() 메서드를 사용하여 NavGraph를 만들고 이를 NavHost에 직접 전달합니다.
  • 프래그먼트: 뷰 UI 프레임워크와 함께 프래그먼트를 사용할 때 NavHostFragment를 호스트로 사용합니다. 탐색 그래프를 만드는 방법에는 여러 가지가 있습니다.
    • 프로그래매틱 방식: Kotlin DSL을 사용하여 NavGraph를 만들고 이를 NavHostFragment에 직접 적용합니다.
      • 프래그먼트와 Compose에 모두 Kotlin DSL과 함께 사용되는 createGraph() 함수는 동일합니다.
    • XML: 탐색 호스트와 그래프를 XML로 직접 작성합니다.
    • Android 스튜디오 편집기: Android 스튜디오의 GUI 편집기를 사용하여 그래프를 XML 리소스 파일로 만들고 조정합니다.

Compose

Compose에서 직렬화 가능한 객체 또는 클래스를 사용하여 경로를 정의합니다. 경로는 목적지에 도착하는 방법을 설명하며 목적지에 필요한 모든 정보를 포함합니다.

@Serializable 주석을 사용하여 경로 유형에 필요한 직렬화 및 역직렬화 메서드를 자동으로 만듭니다. 이 주석은 Kotlin 직렬화 플러그인에서 제공합니다. 이 안내에 따라 이 플러그인을 추가합니다.

경로를 정의한 후 NavHost 컴포저블을 사용하여 탐색 그래프를 만듭니다. 다음 예를 참고하세요.

@Serializable
object Profile
@Serializable
object FriendsList

val navController = rememberNavController()

NavHost(navController = navController, startDestination = Profile) {
    composable<Profile> { ProfileScreen( /* ... */ ) }
    composable<FriendsList> { FriendsListScreen( /* ... */ ) }
    // Add more destinations similarly.
}
  1. 직렬화 가능한 객체는 두 경로인 ProfileFriendsList를 각각 나타냅니다.
  2. NavHost 컴포저블 호출은 NavController 및 시작 대상의 경로를 전달합니다.
  3. NavHost에 전달된 람다는 궁극적으로 NavController.createGraph()를 호출하고 NavGraph를 반환합니다.
  4. 각 경로는 결과 NavGraph에 목적지를 추가하는 NavGraphBuilder.composable<T>()에 유형 인수로 제공됩니다.
  5. composable에 전달된 람다는 NavHost가 해당 대상에 표시하는 내용입니다.

람다 이해

NavGraph를 만드는 람다를 더 잘 이해하려면, 또한 이를 고려하여 이전 스니펫과 동일한 그래프를 빌드하려면 NavController.createGraph()를 사용하여 별도로 NavGraph를 만들고 NavHost에 이를 직접 전달하면 됩니다.

val navGraph by remember(navController) {
  navController.createGraph(startDestination = Profile)) {
    composable<Profile> { ProfileScreen( /* ... */ ) }
    composable<FriendsList> { FriendsListScreen( /* ... */ ) }
  }
}
NavHost(navController, navGraph)

인수 전달

대상에 데이터를 전달해야 하는 경우 매개변수가 있는 클래스로 경로를 정의합니다. 예를 들어 Profile 경로는 name 매개변수가 있는 데이터 클래스입니다.

@Serializable
data class Profile(val name: String)

해당 대상에 인수를 전달해야 할 때마다 라우트 클래스의 인스턴스를 만들어 인수를 클래스 생성자에 전달합니다.

선택적 인수의 경우 기본값이 있는 null 허용 필드를 만듭니다.

@Serializable
data class Profile(val nickname: String? = null)

경로 인스턴스 가져오기

NavBackStackEntry.toRoute() 또는 SavedStateHandle.toRoute()로 경로 인스턴스를 가져올 수 있습니다. composable()를 사용하여 대상을 만들 때 NavBackStackEntry를 매개변수로 사용할 수 있습니다.

@Serializable
data class Profile(val name: String)

val navController = rememberNavController()

NavHost(navController = navController, startDestination = Profile(name="John Smith")) {
    composable<Profile> { backStackEntry ->
        val profile: Profile = backStackEntry.toRoute()
        ProfileScreen(name = profile.name) }
}

이 스니펫에서 다음 사항에 유의하세요.

  • Profile 경로는 "John Smith"name의 인수로 사용하여 탐색 그래프의 시작 대상을 지정합니다.
  • 대상 자체가 composable<Profile>{} 블록입니다.
  • ProfileScreen 컴포저블은 자체 name 인수에 profile.name 값을 사용합니다.
  • 따라서 값 "John Smith"ProfileScreen로 전달됩니다.

간단한 예시

다음은 NavControllerNavHost가 함께 작동하는 전체 예시입니다.

@Serializable
data class Profile(val name: String)

@Serializable
object FriendsList

// Define the ProfileScreen composable.
@Composable
fun ProfileScreen(
    profile: Profile
    onNavigateToFriendsList: () -> Unit,
  ) {
  Text("Profile for ${profile.name}")
  Button(onClick = { onNavigateToFriendsList() }) {
    Text("Go to Friends List")
  }
}

// Define the FriendsListScreen composable.
@Composable
fun FriendsListScreen(onNavigateToProfile: () -> Unit) {
  Text("Friends List")
  Button(onClick = { onNavigateToProfile() }) {
    Text("Go to Profile")
  }
}

// Define the MyApp composable, including the `NavController` and `NavHost`.
@Composable
fun MyApp() {
  val navController = rememberNavController()
  NavHost(navController, startDestination = Profile(name = "John Smith")) {
    composable<Profile> { backStackEntry ->
        val profile: Profile = backStackEntry.toRoute()
        ProfileScreen(
            profile = profile,
            onNavigateToFriendsList = {
                navController.navigate(route = FriendsList)
            }
        )
    }
    composable<FriendsList> {
      FriendsListScreen(
        onNavigateToProfile = {
          navController.navigate(
            route = Profile(name = "Aisha Devi")
          )
        }
      )
    }
  }
}

스니펫에서 알 수 있듯이 NavController를 컴포저블에 전달하는 대신 이벤트를 NavHost에 노출합니다. 즉, 컴포저블에는 NavController.navigate()를 호출하는 람다를 NavHost가 전달하는 () -> Unit 유형의 매개변수가 있어야 합니다.

프래그먼트

이전 섹션에서 설명한 대로 프래그먼트를 사용할 때 Kotlin DSL, XML 또는 Android 스튜디오 편집기를 사용하여 프로그래매틱 방식으로 탐색 그래프를 만들 수 있습니다.

다음 섹션에서는 이러한 다양한 접근 방식을 자세히 설명합니다.

프로그래매틱 방식

Kotlin DSL은 프래그먼트를 사용하여 프로그래매틱 방식으로 탐색 그래프를 만드는 방법을 제공합니다. 이는 XML 리소스 파일을 사용하는 것보다 여러 면에서 더 깔끔하고 현대적인 방법입니다.

두 화면 탐색 그래프를 구현하는 다음 예시를 생각해 보세요.

먼저 app:navGraph 요소를 포함해서는 안 되는 NavHostFragment를 만들어야 합니다.

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</FrameLayout>

그런 다음 NavHostFragmentidNavController.findNavController에 전달합니다. 이렇게 하면 NavController가 NavHostFragment와 연결됩니다.

이후에 NavController.createGraph()를 호출하면 그래프가 NavController에 연결되고 따라서 NavHostFragment에도 연결됩니다.

@Serializable
data class Profile(val name: String)

@Serializable
object FriendsList

// Retrieve the NavController.
val navController = findNavController(R.id.nav_host_fragment)

// Add the graph to the NavController with `createGraph()`.
navController.graph = navController.createGraph(
    startDestination = Profile(name = "John Smith")
) {
    // Associate each destination with one of the route constants.
    fragment<ProfileFragment, Profile> {
        label = "Profile"
    }

    fragment<FriendsListFragment, FriendsList>() {
        label = "Friends List"
    }

    // Add other fragment destinations similarly.
}

이러한 방식으로 DSL을 사용하는 것은 Compose에 관한 이전 섹션에서 설명한 워크플로와 매우 유사합니다. 예를 들어 NavController.createGraph() 함수는 두 경우에 모두 NavGraph를 생성합니다. 마찬가지로 NavGraphBuilder.composable()은 그래프에 컴포저블 대상을 추가하고 여기서는 NavGraphBuilder.fragment()가 프래그먼트 대상을 추가합니다.

Kotlin DSL을 사용하는 방법에 관한 자세한 내용은 NavGraphBuilder DSL로 그래프 빌드를 참고하세요.

XML

XML을 직접 작성할 수 있습니다. 다음 예시는 이전 섹션의 두 화면 예시를 반영하며 해당 예시와 같습니다.

먼저 NavHostFragment를 만듭니다. 이는 실제 탐색 그래프가 포함된 탐색 호스트 역할을 합니다.

NavHostFragment의 최소 구현은 다음과 같습니다.

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:navGraph="@navigation/nav_graph" />

</FrameLayout>

NavHostFragment에는 app:navGraph 속성이 포함되어 있습니다. 이 속성을 사용하여 탐색 그래프를 탐색 호스트에 연결합니다. 다음은 그래프를 구현할 수 있는 방법을 보여주는 예시입니다.

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/nav_graph"
    app:startDestination="@id/profile">

    <fragment
        android:id="@+id/profile"
        android:name="com.example.ProfileFragment"
        android:label="Profile">

        <!-- Action to navigate from Profile to Friends List. -->
        <action
            android:id="@+id/action_profile_to_friendslist"
            app:destination="@id/friendslist" />
    </fragment>

    <fragment
        android:id="@+id/friendslist"
        android:name="com.example.FriendsListFragment"
        android:label="Friends List" />

    <!-- Add other fragment destinations similarly. -->
</navigation>

작업을 사용하여 다양한 대상 간의 연결을 정의합니다. 이 예시에서 profile 프래그먼트에는 friendslist로 이동하는 작업이 포함되어 있습니다. 자세한 내용은 탐색 작업 및 프래그먼트 사용을 참고하세요.

편집기

Android 스튜디오의 Navigation Editor를 사용하여 앱의 탐색 그래프를 관리할 수 있습니다. 이는 이전 섹션에서 본 것처럼 기본적으로 NavigationFragment XML을 만들고 수정하는 데 사용할 수 있는 GUI입니다.

자세한 내용은 탐색 편집기를 참고하세요.

중첩 그래프

중첩 그래프를 사용할 수도 있습니다. 여기에는 그래프를 탐색 대상으로 사용하는 작업이 포함됩니다. 자세한 내용은 중첩 그래프를 참고하세요.

추가 자료

핵심 탐색 개념에 관한 자세한 내용은 다음 가이드를 참고하세요.

  • 개요: 탐색 구성요소에 관한 일반적인 개요를 읽어 보세요.
  • 활동 대상: 사용자를 활동으로 안내하는 대상을 구현하는 방법의 예시입니다.
  • 대화상자 대상: 사용자를 대화상자로 안내하는 대상을 만드는 방법의 예시입니다.
  • 대상으로 이동: 한 대상에서 다른 대상으로 이동하는 방법을 설명하는 자세한 가이드입니다.
  • 중첩 그래프: 하나의 탐색 그래프를 다른 그래프 내에 중첩하는 방법에 관한 자세한 가이드입니다.