Google은 흑인 공동체를 위한 인종 간 평등을 진전시키기 위해 노력하고 있습니다. Google에서 어떤 노력을 하고 있는지 확인하세요.

Kotlin DSL을 사용하여 프로그래매틱 방식으로 그래프 빌드

이동 구성요소는 Kotlin 기반의 도메인별 언어인 DSL을 제공하며, DSL은 Kotlin의 형식 안전 빌더를 사용합니다. 이 API를 사용하면 XML 리소스 내부가 아니라 Kotlin 코드에서 그래프를 명시적으로 구성할 수 있습니다. 앱의 이동을 동적으로 빌드하고자 할 때 유용합니다. 예를 들어 앱에서 외부 웹 서비스의 이동 구성을 다운로드 및 캐시한 다음, 이 구성을 사용해 활동의 onCreate() 기능에서 이동 그래프를 동적으로 빌드할 수 있습니다.

종속 항목

Kotlin DSL을 사용하려면 앱의 build.gradle 파일에 다음 종속 항목을 추가하세요.

dependencies {
    def nav_version = "2.3.1"

    api "androidx.navigation:navigation-fragment-ktx:$nav_version"
}

그래프 빌드

Sunflower 앱을 기반으로 기본적인 예시부터 살펴보겠습니다. 이 예시에는 homeplant_detail이라는 두 개의 대상이 있습니다. 사용자가 처음으로 앱을 실행하면 home 대상이 표시됩니다. 이 대상은 사용자의 정원에 있는 식물 목록을 표시합니다. 사용자가 식물 중 하나를 선택하면 앱이 plant_detail 대상으로 이동합니다.

그림 1은 plant_detail 대상에서 요구하는 인수와 앱이 home에서 plant_detail로 이동할 때 사용하는 작업인 to_plant_detail을 함께 보여줍니다.

Sunflower 앱에는 두 개의 대상과 두 대상을 연결하는 작업이 있습니다.
그림 1. Sunflower 앱에는 homeplant_detail이라는 두 개의 대상과 두 대상을 연결하는 작업이 있습니다.

Kotlin DSL 이동 그래프의 호스트 만들기

그래프를 빌드하는 방법과 관계없이 NavHost에 그래프를 호스팅해야 합니다. Sunflower에서는 프래그먼트를 사용하므로 다음 예시에 표시된 것과 같이 FragmentContainerView 내부에 NavHostFragment를 사용해 보겠습니다.

<!-- activity_garden.xml -->
<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"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true" />

</FrameLayout>

그래프는 XML 리소스로 정의되는 대신 프로그래미틱 방식으로 만들어지므로 이 예시에서는 app:navGraph 속성이 설정되지 않습니다.

그래프 상수 만들기

XML 기반 이동 그래프를 작업할 때 Android 빌드 프로세스에서는 그래프 리소스 파일을 파싱하고 그래프에서 정의된 각 id 속성에 상수를 숫자로 정의합니다. 이 상수는 생성된 리소스 클래스인 R.id를 통해 코드에서 액세스할 수 있습니다.

예를 들어 다음의 XML 그래프 스니펫은 id, home을 사용해 프래그먼트 대상을 선언합니다.

<navigation ...>
   <fragment android:id="@+id/home" ... />
   ...
</navigation>

빌드 프로세스에서는 대상과 연결된 상수 값인 R.id.home을 만듭니다. 그러면 이 상수 값을 사용해 코드에서 대상을 참조할 수 있습니다.

Kotlin DSL을 사용해 그래프를 프로그래매틱 방식으로 빌드하면 상수를 파싱하고 생성하는 프로세스가 발생하지 않습니다. 대신 id 값이 있는 모든 대상, 작업, 인수에 자체 상수를 정의해야 합니다. 구성이 변경되어도 각 ID는 고유하고 일관되어야 합니다.

상수를 만드는 체계적인 방법 중 하나는 다음 예시와 같이 상수를 통계적으로 정의하는 Kotlin object의 중첩 세트를 만드는 것입니다.

object nav_graph {

    const val id = 1 // graph id

    object dest {
        const val home = 2
        const val plant_detail = 3
    }

    object action {
        const val to_plant_detail = 4
    }

    object args {
        const val plant_id = "plantId"
    }
}

이 구조를 사용하면 다음 예시와 같이 객체 호출을 서로 연결하여 코드에서 ID 값에 액세스할 수 있습니다.

nav_graph.id                     // graph id
nav_graph.dest.home              // home destination id
nav_graph.action.to_plant_detail // action home -> plant_detail id
nav_graph.args.plant_id          // destination argument name

ID의 초기 집합을 정의하고 나면 이동 그래프를 빌드할 수 있습니다. NavController.createGraph() 확장 함수를 사용해 NavGraph을(를) 만듭니다. 이때 그래프에는 id을(를), startDestination에는 ID 값을 패싱하고 그래프의 구조를 정의하는 후행 람다도 포함합니다.

활동의 onCreate() 함수에서 그래프를 빌드할 수 있습니다. createGraph()Navgraph를 반환하는데, 이는 다음 예시와 같이 NavHost와 연결된 NavControllergraph 속성에 할당할 수 있습니다.

class GardenActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_garden)

        val navHostFragment = supportFragmentManager
                .findFragmentById(R.id.nav_host) as NavHostFragment

        navHostFragment.navController.apply {
            graph = createGraph(nav_graph.id, nav_graph.dest.home) {
                fragment<HomeViewPagerFragment>(nav_graph.dest.home) {
                    label = getString(R.string.home_title)
                    action(nav_graph.action.to_plant_detail) {
                        destinationId = nav_graph.dest.plant_detail
                    }
                }
                fragment<PlantDetailFragment>(nav_graph.dest.plant_detail) {
                    label = getString(R.string.plant_detail_title)
                    argument(nav_graph.args.plant_id) {
                        type = NavType.StringType
                    }
                }
            }
        }
    }
}

이 예에서 후행 람다는 fragment() DSL 빌더 함수를 사용하여 두 개의 프래그먼트 대상을 정의합니다. 이 함수에는 대상의 ID가 필요합니다. 이 함수는 대상 label과 같이 추가 구성에 사용할 람다(선택사항)와 작업, 인수, 딥 링크에 사용할 삽입된 빌더 함수도 허용합니다.

각 대상의 UI를 관리하는 Fragment 클래스는 매개변수화된 유형(<>)으로 꺾쇠 괄호 안에 포함됩니다. 이는 XML을 사용해 정의된 프래그먼트 대상에 android:name 속성을 설정하는 것과 동일한 효과가 있습니다.

그래프를 만들고 설정하고 나면 다음 예시와 같이 NavController.navigate()를 사용하여 home에서 plant_detail로 이동할 수 있습니다.

private fun navigateToPlant(plantId: String) {

    val args = bundleOf(nav_graph.args.plant_id to plantId)

    findNavController().navigate(nav_graph.action.to_plant_detail, args)
}

지원되는 대상 유형

Kotlin DSL은 Fragment, Activity, NavGraph 대상을 지원하며, 각 항목에는 대상을 빌드하고 구성할 때 사용할 수 있는 자체적인 인라인 확장 함수가 있습니다.

프래그먼트 대상

fragment() DSL 함수는 구현하는 Fragment 클래스를 대상으로 매개변수화할 수 있습니다. 이 함수는 대상에 할당할 고유 ID와 추가 구성을 제공할 수 있는 람다를 사용합니다.

fragment<FragmentDestination>(nav_graph.dest.fragment_dest_id) {
   label = getString(R.string.fragment_title)
   // arguments, actions, deepLinks...
}

활동 대상

activity() DSL 함수는 고유 ID를 사용해 대상에 할당하지만 구현하는 활동 클래스에 매개변수화되지는 않습니다. 대신 후행 람다에서 선택적으로 activityClass을(를) 설정할 수 있습니다. 이러한 유연성을 활용해 암시적 인텐트에서 시작된 활동의 대상을 정의할 수 있으며, 이때 명시적 활동 클래스는 적합하지 않습니다. 프래그먼트 대상과 마찬가지로 라벨 및 모든 인수를 정의하고 구성할 수도 있습니다.

activity(nav_graph.dest.activity_dest_id) {
    label = getString(R.string.activity_title)
    // arguments, actions, deepLinks...

    activityClass = ActivityDestination::class
}

navigation() DSL 함수를 사용해 중첩된 이동 그래프를 빌드할 수 있습니다. 다른 대상 유형과 마찬가지로 이 DSL 함수는 그래프에 할당할 ID, 그래프의 시작 대상 ID, 추가로 그래프를 구성할 람다 등 세 가지의 인수를 사용합니다. 람다에 사용할 수 있는 유효한 요소로는 인수, 작업, 기타 대상, 딥 링크, 라벨이 있습니다.

navigation(nav_graph.dest.nav_graph_dest, nav_graph.dest.start_dest) {
   // label, arguments, actions, other destinations, deep links
}

맞춤 대상 지원

addDestination()을 사용해 다음 예시와 같이 기본적으로는 직접 지원되지 않는 맞춤 대상 유형을 Kotlin DSL에 추가할 수 있습니다.

// The NavigatorProvider is retrieved from the NavController
val customDestination = navigatorProvider[CustomNavigator::class].createDestination().apply {
    id = nav_graph.dest.custom_dest_id
}
addDestination(customDestination)

1진법 더하기 연산자(+)를 사용하여 새로 구성된 대상을 그래프에 직접 추가할 수도 있습니다.

// The NavigatorProvider is retrieved from the NavController
+navigatorProvider[CustomNavigator::class].createDestination().apply {
    id = nav_graph.dest.custom_dest_id
}

대상 인수 제공

모든 대상 유형에 선택사항 또는 필수 인수를 정의할 수 있습니다. 인수를 정의하려면 모든 대상 빌더 유형의 기본 클래스인 NavDestinationBuilder에서 argument() 함수를 호출하세요. 이 함수는 인수 이름을 NavArgument를 구성하는 데 사용할 람다 및 String으로 사용합니다. 람다 내에서 인수 데이터 유형, 기본값(해당하는 경우), 인수 값이 null인지를 지정할 수 있습니다.

fragment<PlantDetailFragment>(nav_graph.dest.plant_detail) {
    label = getString(R.string.plant_details_title)
    argument(nav_graph.args.plant_name) {
        type = NavType.StringType
        defaultValue = getString(R.string.default_plant_name)
        nullable = true  // default false
    }
}

defaultValue가 제공되면 type은 선택사항입니다. 이 경우 type을 지정하지 않으면 유형이 defaultValue에서 유추됩니다. defaultValuetype이 모두 제공되는 경우 유형이 일치해야 합니다. 인수 유형의 전체 목록은 NavType을 참조하세요.

작업

루트 이동 그래프 내에서 전역 작업을 포함한 모든 대상의 작업을 정의할 수 있습니다. 작업을 정의하려면 NavDestinationBuilder.action() 함수로 함수와 람다에 ID를 제공하여 추가 구성을 제공해야 합니다.

다음 예시에서는 destinationId, 전환 애니메이션, 팝 및 단일 상단 동작을 사용해 작업을 빌드합니다.

action(nav_graph.action.to_plant_detail) {
    destinationId = nav_graph.dest.plant_detail
    navOptions {
        anim {
            enter = R.anim.nav_default_enter_anim
            exit = R.anim.nav_default_exit_anim
            popEnter = R.anim.nav_default_pop_enter_anim
            popExit = R.anim.nav_default_pop_exit_anim
        }
        popUpTo(nav_graph.dest.start_dest) {
            inclusive = true // default false
        }
        // if popping exclusively, you can specify popUpTo as
        // a property. e.g. popUpTo = nav_graph.dest.start_dest
        launchSingleTop = true // default false
    }
}

딥 링크

XML 기반 이동 그래프와 마찬가지로 모든 대상에 딥 링크를 추가할 수 있습니다. 대상의 딥 링크 만들기에서 정의된 것과 동일한 절차가 Kotlin DSL을 사용하는 명시적 딥 링크 만들기 프로세스에도 적용됩니다.

하지만 암시적 딥 링크를 만들 때는 <deepLink> 요소를 분석할 수 있는 XML 이동 리소스가 없습니다. 따라서 AndroidManifest.xml 파일에 <nav-graph> 요소를 배치할 필요가 없으며 활동에 수동으로 인텐트 필터를 추가해야 합니다. 제공하는 인텐트 필터는 앱 딥 링크의 기본 URL 패턴과 일치해야 합니다.

deepLink() DSL 함수를 사용하면 개별 딥 링크 대상별로 보다 구체적인 URI 패턴을 제공할 수 있습니다. 이 함수는 다음 예시와 같이 URI 패턴에서 String을 허용합니다.

deepLink("http://www.example.com/plants/")

추가할 수 있는 딥 링크 URI의 수에는 제한이 없습니다. deepLink()를 호출할 때마다 대상과 관련된 내부 목록에 새로운 딥 링크가 추가됩니다.

다음은 경로 및 쿼리 기반 매개변수도 정의하는 좀 더 복잡한 암시적 딥 링크 시나리오입니다.

val baseUri = "http://www.example.com/plants"

fragment<PlantDetailFragment>(nav_graph.dest.plant_detail) {
    label = getString(R.string.plant_details_title)
    deepLink("${baseUri}/{id}")
    deepLink("${baseUri}/{id}?name={plant_name}")
    argument(nav_graph.args.plant_id) {
       type = NavType.IntType
    }
    argument(nav_graph.args.plant_name) {
        type = NavType.StringType
        nullable = true
    }
}

문자열 보간 유형을 사용하면 정의를 단순하게 만들 수 있습니다.

ID 만들기

이동 라이브러리의 경우 그래프 요소에 사용되는 ID 값은 고유한 정수이자 구성이 변경되어도 일정하게 유지되어야 합니다. 이러한 ID를 만드는 데는 그래프 상수 만들기에 나온 것처럼 ID를 정적 상수로 정의하는 방법이 있습니다. XML에서 정적 리소스 ID를 리소스로 정의할 수 있습니다. 또는 동적으로 ID를 구성해도 됩니다. 예를 들어 참조할 때마다 증가하는 시퀀스 카운터를 만들어 보세요.

object nav_graph {
    // Counter for id's. First ID will be 1.
    var id_counter = 1

    val id = id_counter++

    object dest {
       val home = id_counter++
       val plant_detail = id_counter++
    }

    object action {
       val to_plant_detail = id_counter++
    }

    object args {
       const val plant_id = "plantId"
    }
}

제한사항

  • Safe Args 플러그인은 Kotlin DSL과 호환되지 않습니다. 플러그인이 DirectionsArguments 클래스를 생성하기 위해 XML 리소스 파일을 찾기 때문입니다.