다중 모듈 프로젝트를 위한 탐색 권장사항

탐색 그래프는 다음을 원하는 대로 조합하여 구성할 수 있습니다.

  • 단일 대상(예: <fragment> 대상)
  • 일련의 관련 대상을 캡슐화하는 중첩 그래프
  • 중첩된 것처럼 다른 탐색 그래프 파일을 삽입할 수 있는 <include> 요소

이러한 유연성을 통해 작은 탐색 그래프를 함께 결합하여 앱의 완전한 탐색 그래프를 구성할 수 있습니다. 이러한 작은 탐색 그래프가 별도의 모듈에서 제공되더라도 마찬가지입니다.

이 주제의 예에서는 각 기능 모듈이 한 기능에 중점을 두고 이 기능을 구현하는 데 필요한 모든 대상을 캡슐화하는 단일 탐색 그래프를 제공합니다. 프로덕션 앱에는 이 상위 수준 기능 모듈의 구현 세부정보인 하위 모듈이 하위 수준에 여러 개 있을 수 있습니다. 이러한 각 기능 모듈은 직접 또는 간접적으로 app 모듈에 포함됩니다. 이 문서에 사용된 다중 모듈 애플리케이션 예의 구조는 다음과 같습니다.

샘플 다중 모듈 애플리케이션의 종속 항목 그래프
예시 앱의 시작 대상
그림 1. 예시 앱의 앱 아키텍처 및 시작 대상

각 기능 모듈은 자체 탐색 그래프와 대상이 있는 독립된 단위입니다. app 모듈은 각각에 종속되므로 다음과 같이 build.gradle 파일에 구현 세부정보로 추가됩니다.

Groovy

dependencies {
    ...
    implementation project(":feature:home")
    implementation project(":feature:favorites")
    implementation project(":feature:settings")

Kotlin

dependencies {
    ...
    implementation(project(":feature:home"))
    implementation(project(":feature:favorites"))
    implementation(project(":feature:settings"))

app 모듈의 역할

app 모듈은 앱의 완전한 그래프를 제공하고 NavHost를 UI에 추가합니다. app 모듈의 탐색 그래프 내에서 <include>를 사용하여 라이브러리 그래프를 참조할 수 있습니다. <include> 사용은 기능적으로 중첩 그래프를 사용하는 것과 같지만 <include>는 다음 예와 같이 다른 프로젝트 모듈 또는 라이브러리 프로젝트의 그래프를 지원합니다.

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph"
    app:startDestination="@id/home_nav_graph">

    <include app:graph="@navigation/home_navigation" />
    <include app:graph="@navigation/favorites_navigation" />
    <include app:graph="@navigation/settings_navigation" />
</navigation>

라이브러리가 최상위 탐색 그래프에 포함되면 필요에 따라 라이브러리 그래프로 이동할 수 있습니다. 예를 들어 다음과 같이 탐색 그래프의 프래그먼트에서 설정 그래프로 이동하는 작업을 만들 수 있습니다.

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph"
    app:startDestination="@id/home_nav_graph">

    <include app:graph="@navigation/home_navigation" />
    <include app:graph="@navigation/favorites_navigation" />
    <include app:graph="@navigation/settings_navigation" />

    <fragment
        android:id="@+id/random_fragment"
        android:name="com.example.android.RandomFragment"
        android:label="@string/fragment_random" >
        <!-- Launch into Settings Navigation Graph -->
        <action
            android:id="@+id/action_random_fragment_to_settings_nav_graph"
            app:destination="@id/settings_nav_graph" />
    </fragment>
</navigation>

여러 기능 모듈이 로그인 그래프와 같은 일반적인 대상 집합을 참조해야 하는 경우 이러한 일반적인 대상을 각 기능 모듈의 탐색 그래프에 포함하면 안 됩니다. 대신 이러한 일반적인 대상을 app 모듈의 탐색 그래프에 추가하세요. 그런 다음 각 기능 모듈은 기능 모듈 전체를 탐색하여 이러한 일반적인 대상으로 이동할 수 있습니다.

이전 예에서 작업은 @id/settings_nav_graph의 탐색 대상을 지정합니다. 이 ID는 포함된 그래프 @navigation/settings_navigation. 내에서 정의된 대상을 나타냅니다.

앱 모듈의 최상위 탐색

탐색 구성요소에는 NavigationUI 클래스가 포함되어 있습니다. 이 클래스에는 상단 앱 바, 탐색 창 및 하단 탐색으로 탐색을 관리하는 정적 메서드가 포함되어 있습니다. 앱의 최상위 대상이 기능 모듈에서 제공하는 UI 요소로 구성되어 있다면 app 모듈이 최상위 탐색 및 UI 요소를 배치하기에 적합한 위치입니다. 앱 모듈은 공동작업 기능 모듈에 종속되므로 앱 모듈 내에 정의된 코드에서 모든 대상에 액세스할 수 있습니다. 즉, 항목 ID가 대상 ID와 일치하면 NavigationUI를 사용하여 대상을 메뉴 항목에 연결할 수 있습니다.

그림 2에서 예로 든 app 모듈은 기본 활동에서 BottomNavigationView를 정의합니다. 메뉴의 메뉴 항목 ID는 라이브러리 그래프의 탐색 그래프 ID와 일치합니다.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@id/home_nav_graph"
        android:icon="@drawable/ic_home"
        android:title="Home"
        app:showAsAction="ifRoom"/>

    <item
        android:id="@id/favorites_nav_graph"
        android:icon="@drawable/ic_favorite"
        android:title="Favorites"
        app:showAsAction="ifRoom"/>

    <item
        android:id="@id/settings_nav_graph"
        android:icon="@drawable/ic_settings"
        android:title="Settings"
        app:showAsAction="ifRoom" />
</menu>

NavigationUI하단 탐색을 처리하도록 하려면 다음 예와 같이 기본 활동 클래스의 onCreate()에서 setupWithNavController()를 호출합니다.

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController

    findViewById<BottomNavigationView>(R.id.bottom_nav)
            .setupWithNavController(navController)
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    NavHostFragment navHostFragment =
            (NavHostFragment) supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
    NavController navController = navHostFragment.getNavController();
    BottomNavigationView bottomNav = findViewById(R.id.bottom_nav);

    NavigationUI.setupWithNavController(bottomNav, navController);
}

이 코드를 사용하면 NavigationUI는 사용자가 하단 탐색 항목을 클릭할 때 적절한 라이브러리 그래프로 이동합니다.

일반적으로 앱 모듈이 기능 모듈의 탐색 그래프 내에 깊이 삽입된 특정 대상에 심하게 종속되는 것은 좋지 않습니다. 대부분의 경우 개발자는 삽입 또는 포함된 탐색 그래프의 진입점에 대해서만 앱 모듈이 알기를 바랍니다. 이는 기능 모듈 외에도 적용됩니다. 라이브러리의 탐색 그래프 내에 깊이 있는 대상에 연결해야 한다면 딥 링크를 사용하는 것이 좋습니다. 딥 링크는 라이브러리가 다른 라이브러리의 탐색 그래프에 있는 대상으로 이동하는 유일한 방법이기도 합니다.

기능 모듈 탐색

컴파일 시 독립된 기능 모듈은 서로 확인할 수 없으므로 ID를 사용하여 다른 모듈의 대상으로 이동할 수 없습니다. 대신 딥 링크를 사용하여 암시적 딥 링크와 연결된 대상으로 직접 이동합니다.

이전 예를 계속 사용하면서 :feature:home 모듈의 버튼에서 :feature:settings 모듈에 중첩된 대상으로 이동해야 한다고 가정해보겠습니다. 다음과 같이 설정 탐색 그래프에서 대상에 딥 링크를 추가하면 됩니다.

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/settings_nav_graph"
    app:startDestination="@id/settings_fragment_one">

    ...

    <fragment
        android:id="@+id/settings_fragment_two"
        android:name="com.example.google.login.SettingsFragmentTwo"
        android:label="@string/settings_fragment_two" >

        <deepLink
            app:uri="android-app://example.google.app/settings_fragment_two" />
    </fragment>
</navigation>

그런 다음 홈 프래그먼트에서 버튼의 onClickListener에 다음 코드를 추가합니다.

Kotlin

button.setOnClickListener {
    val request = NavDeepLinkRequest.Builder
        .fromUri("android-app://example.google.app/settings_fragment_two".toUri())
        .build()
    findNavController().navigate(request)
}

Java

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        NavDeepLinkRequest request = NavDeepLinkRequest.Builder
            .fromUri(Uri.parse("android-app://example.google.app/settings_fragment_two"))
            .build();
        NavHostFragment.findNavController(this).navigate(request);
    }
});

작업 또는 대상 ID를 사용하는 탐색과 달리 모든 그래프의 어떤 URI로도 이동할 수 있고 모듈 간에도 이동할 수 있습니다.

URI를 사용하여 이동할 때 백 스택은 재설정되지 않습니다. 이 동작은 이동 중 백 스택이 교체되는 명시적 딥 링크 탐색과는 다릅니다.