navigation graph는 다음을 원하는 대로 조합하여 구성할 수 있다.
이러한 유연성 덕분에, smaller navigation graph들을 결합하여 앱의 complete navigation graph를 구성할 수 있다. 심지어 이러한 smaller navigation graph가 개별 모듈에서 제공되는 경우에도 가능하다.
Navigation graph
앱의 화면 전환을 관리하는 구조로 앱의 여러 화면(목적지)을 정의하고, 이들 간의 연결(전환 경로)을 설정한다
- 단일 목적지 (Singular Destination)
앱의 특정 화면 ex ) < fragment> 태그를 사용하여 정의- 중첩 그래프 (Nested Graph)
관련된 여러 목적지를 하나로 묶어 관리하는 그래프로 복잡한 네비게이션 구조를 깔끔하게 유지할 수 있다.- < include> 요소
다른 네비게이션 그래프 파일을 포함할 수 있는 태그로 포함된 그래프는 마치 중첩 그래프처럼 동작한다. 예를 들어, 여러 모듈에 나뉜 그래프를 하나의 앱 네비게이션 그래프에 합칠 수 있다.각 모듈에서 별도로 제공된 네비게이션 그래프를, 앱 모듈에서 통합하여 전체 네비게이션 그래프로 만들 수 있다. 모듈별로 분리된 그래프라도 < include> 태그를 사용하여 하나의 큰 네비게이션 그래프로 결합할 수 있다
각 feature 모듈이 하나의 기능(예: 홈, 설정, 즐겨찾기 등)에 초점을 맞춘다. 해당 기능을 구현하는 데 필요한 모든 목적지들을 캡슐화하는 single navigation graph를 제공한다.
실제 프로덕션 앱에서는 이 고수준 feature 모듈의 세부 구현을 담당하는 더 낮은 수준의 하위 모듈들이 다수 존재할 수 있다. 이러한 각 feature 모듈은 직접적으로 또는 간접적으로 앱 모듈에 포함된다.
- feature 모듈은 해당 기능을 구현하는 데 필요한 모든 화면(목적지)과 화면 간의 전환(네비게이션)을 자신의 네비게이션 그래프에 포함시킨다. 즉, 기능별로 독립적인 navigation graph를 가진다.
- 각 feature 모듈은 자신의 기능에만 집중하고, 관련된 화면과 네비게이션 정보를 자체적으로 관리한다
- 앱 모듈은 이러한 feature 모듈들을 통합하여 앱 전체 네비게이션 그래프를 구성한다
- 실제 앱에서는 feature 모듈의 세부 구현(예: 데이터 처리, 네트워크 호출)을 담당하는 하위 모듈이 있을 수 있으며, 이는 feature 모듈 내부에 포함된다
이 문서에서 사용된 멀티 모듈 애플리케이션의 구조는 다음과 같다.
각 모듈이 독립적으로 동작하면서도, 앱 모듈을 통해 하나로 연결된다
각 feature 모듈은 자체 네비게이션 그래프와 목적지를 포함하는 독립적인 단위다.
앱 모듈은 이러한 feature 모듈 각각에 의존하며, 이를 build.gradle 파일에 구현 세부사항으로 추가한다.
dependencies {
...
implementation project(":feature:home")
implementation project(":feature:favorites")
implementation project(":feature:settings")
앱 모듈은 앱의 전체 네비게이션 그래프를 제공하고, UI에 NavHost를 추가하는 역할을 담당한다.
앱 모듈의 네비게이션 그래프에서는 < include>를 사용하여 라이브러리 그래프를 참조할 수 있다.
< include>를 사용하는 것은 중첩된 그래프(nested graph)를 사용하는 것과 기능적으로 동일하지만,
< include>는 다른 프로젝트 모듈이나 라이브러리 프로젝트의 그래프도 지원한다.
- 앱 모듈은 앱의 모든 화면 전환 경로(네비게이션 그래프)를 통합관리 한다.
- 다른 feature 모듈에서 정의된 개별 네비게이션 그래프를 가져와 전체 그래프로 결합한다.
- NavHost는 앱 화면의 네비게이션을 담당하는 컨테이너 역할을 하는데, 앱 모듈의 UI에서 NavHost를 설정하여 사용자 네비게이션 동작을 처리한다.
- 앱 모듈의 네비게이션 그래프는 < include>태그를 사용하여 다른 feature 모듈의 네비게이션 그래프를 가져올 수 있다.
- 중첩된 그래프는 같은 모듈 내에서만 동작하지만 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>
<include> 태그의 graph 속성은 라이브러리의 그래프 파일 이름(home_navigation.xml)을 참조한다.
startDestination은 해당 파일 내 <navigation> 요소의 ID를 참조하며, 특히 @+id를 사용하지 않고,
대신 @id/를 사용하여 기능 모듈(feature module)에서 이미 선언된 ID를 그대로 참조한다.
- include 태그의 graph 속성은 참조할 네비게이션 그래프 파일을 지정한다
- startDestination은 앱을 실행했을 때 가장 먼저 표시할 화면을 설정한다
- @+id : 새로운 ID를 생성/ @id : 이미 존재하는 ID를 참조
- startDestination이 @+id가 아닌 @id를 사용한다
즉, 이미 feature 모듈에서 정의된 ID를 다시 생성하지 않고 그대로 사용한다
라이브러리가 최상위 네비게이션 그래프에 포함되면, 필요에 따라 라이브러리 그래프로 네비게이션할 수 있다. 예를 들어, 네비게이션 그래프의 특정 프래그먼트에서 설정 그래프로 이동하는 액션을 생성할 수 있다.
home_navigation, settings_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" />
<!-- random_fragment에서 settings_nav_graph로 이동하기 위한 액션 ID.
목적지로 settings_navigation 그래프 설정 -->
</fragment>
</navigation>
여러 feature 모듈이 로그인 그래프와 같은 공통 목적지를 참조해야 할 때, 이러한 공통 목적지를 각 feature 모듈의 네비게이션 그래프에 포함하면 안된다. 대신, 공통 목적지는 앱 모듈의 네비게이션 그래프에 추가해야 한다. 그러면 각 feature 모듈이 feature 모듈 간 네비게이션을 통해 이러한 공통 목적지로 이동할 수 있다.
이전 예제에서, 액션은 @id/settings_nav_graph라는 네비게이션 목적지를 지정한다.
이 ID는 @navigation/settings_navigation에 포함된 그래프 내에서 정의된 목적지를 참조한다.
즉, @id/settings_nav_graph처럼 ID를 사용해 공통 그래프 내의 목적지를 참조한다.
Navigation Component는 NavigationUI 클래스를 제공한다.
이 클래스는 상단 앱 바, 네비게이션 드로어, 하단 네비게이션과의 네비게이션을 관리하는 정적 메서드를 포함한다.
- Navigation Component
앱 내 화면 전환(네비게이션)을 쉽게 관리하기 위한 안드로이드 라이브러리
화면 간의 이동, 뒤로가기 동작, 딥 링크를 처리한다- NavigationUI 클래스
Navigation Component와 UI 요소(상단 앱 바, 네비게이션 드로어, 하단 네비게이션 바)를 연결하는 데 사용하는 클래스다
화면 전환과 UI 상호작용을 쉽게 관리하도록 돕는 정적 메서드를 제공한다.
만약 앱의 최상위 목적지가 feature 모듈에서 제공하는 UI 요소로 구성되어 있다면, 앱 모듈은 최상위 네비게이션과 UI 요소를 배치하기에 적합한 장소다. 앱 모듈이 협업하는 feature 모듈에 의존하므로,
feature 모듈의 모든 목적지는 앱 모듈 내에서 정의된 코드로 접근할 수 있다.
이를 통해 NavigationUI를 사용하여 메뉴 항목을 목적지와 연결할 수 있다.
단, 메뉴 항목의 ID가 네비게이션 그래프의 목적지 ID와 일치해야 한다.
- 앱 모듈은 앱의 전체적인 UI와 네비게이션을 관리하며, feature 모듈에서 제공하는 목적지(화면 또는 그래프)를 참조한다.
- feature 모듈은 홈 화면, 설정 화면 등의 독립적인 UI와 네비게이션 그래프를 제공한다
- 앱 모듈은 feature 모듈에 의존하기 때문에, feature 모듈의 모든 네비게이션 목적지를 앱 모듈에서 접근할 수 있다.
- NavigationUI를 사용하면, 앱 모듈의 UI 요소(예: 하단 네비게이션 바, 네비게이션 드로어)의 메뉴 항목을 네비게이션 그래프의 목적지와 쉽게 연결할 수 있다. 단 메뉴 항목의 ID가 네비게이션 그래프 목적지의 ID와 일치해야 한다.
예: 메뉴 항목의 ID가 @id/home_nav_graph라면, 네비게이션 그래프에 @id/home_nav_graph라는 ID가 존재해야 한다.
아래 예제에서는 앱 모듈이 메인 엑티비티에 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"/>
<!--id는 네비게이션 그래프의 id와 동일해야함-->
<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()를 아래처럼 호출하면 된다.
override fun onCreate(savedInstanceStateL Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// NavHostFragment를 가져와 navController 생성
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
// BottomNavigationView를 navController와 연결
findViewById<BottomNavigationView>(R.id.bottom_nav)
.setupWithNavController(navController)
// 하단 네비게이션 바의 메뉴를 클릭하면, 네비게이션 그래프에 정의된 화면으로 전환
}
이 코드를 작성하면, NavigationUI가 사용자가 하단 네비게이션 항목을 클릭했을 때 적절한 라이브러리 그래프로 네비게이션 한다.
- NavigationUI는 네비게이션 그래프와 UI 요소(하단 네비게이션 바 등)를 연결하는 도구다. 이를 사용하면, 메뉴 항목을 클릭했을 때 네비게이션 그래프에 정의된 화면 전환이 자동으로 처리된다.
- setupWithNavController()를 호출하면, BottomNavigationView와 네비게이션 그래프를 연결할 수 있다.
하지만, 앱 모듈이 feature 모듈의 네비게이션 그래프 깊숙히 포함된 특정 목적지에 대해 강한 의존성을 가지는 것은 일반적으로 좋지 않은 관행이다.
대부분의 경우, 앱 모듈은 포함되거나 중첩된 네비게이션 그래프의 진입 지점(entry point)에 대해서만 알아야 한다.
이 원칙은 feature 모듈 외부의 네비게이션 그래프에도 적용된다.
만약 라이브러리의 네비게이션 그래프 내부 깊숙히 위치한 목적지로 연결해야 한다면, 딥 링크를 사용이 권장된다.
딥 링크는 한 라이브러리가 다른 라이브러리의 네비게이션 그래프에 있는 목적지로 네비게이션 할 수 있는 유일한 방법이기도 하다.
- 앱 모듈은 전체 네비게이션 그래프를 관리한다. 하지만, feature 모듈의 네비게이션 그래프 내부 깊숙이 포함된 특정 목적지에 강하게 의존해서는 안 된다.
- 앱 모듈은 feature 모듈의 네비게이션 그래프 전체를 알 필요가 없으며, 진입 지점(entry point)에 대해서만 알면 된다.
예: feature 모듈의 시작 화면(홈 화면, 설정 화면 등)- 만약 feature 모듈의 내부 깊숙이 위치한 특정 목적지로 이동해야 한다면, 딥 링크(Deep Link)를 사용하는 것이 좋다.
- 딥 링크는 한 모듈의 네비게이션 그래프를 다른 모듈에서 안전하게 참조하는 유일한 방법으로, 특정 URL을 통해 앱의 특정 화면으로 직접 이동한다.
컴파일 시점에는 독립적인 feature 모듈끼리는 서로를 참조할 수 없으므로, 다른 모듈의 목적지로 네비게이션 하기 위해 ID를 사용할 수 없다.
대신, 딥 링크를 사용하여 암시적 딥 링크와 연결된 목적지로 직접 이동해야 한다.
컴파일 시점에는 모듈 간 참조가 차단되어 있다.
이전 예제를 이어서 설명하자면, :feature:home 모듈의 버튼에서 :feature:settings 모듈의 네비게이션 그래프에 중첩된 특정 목적지로 네비게이션 해야 한다고 가정해보자. 이를 구현하려면, settings navigation graph의 해당 목적지에 딥 링크를 추가하면 된다.
<?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>
그런 다음, home Fragment에서 버튼의 onClickListener에 아래 코드를 추가한다.
button.setOnClickListener {
val request = NavDeepLinkRequest.Builder // 딥링크 요청 생성
.fromUri("android-app://example.google.app/settings_fragment_two".toUri())
.build()
findNavController().navigate(request)
}
액션 ID나 목적지 ID를 사용한 네비게이션과 달리, URI를 사용하면 그래프 내의어떤 URI로도 네비게이션 할 수 있으며, 이는 모듈 간에도 가능하다.
URI를 사용한 네비게이션에는 Back stack이 초기화되지 않는다.
이는 명시적 딥 링크 네비게이션과는 다른 동작으로, 명시적 딥 링크 네비게이션에서는 네비게이션 시 Back stack이 대체된다.
- URI를 사용한 네비게이션은 Back stack을 초기화하지 않는다.
이전 화면의 Back stack이 그대로 유지된다.
사용자는 뒤로 가기 버튼을 눌렀을 때, 이전 화면으로 돌아갈 수 있다.- 명시적 딥 링크 네비게이션은 Back stack을 대체한다.
딥 링크를 호출하면 현재 Back stack이 초기화되고, 딥 링크 목적지가 새로운 Back stack의 루트(root)가 된다.
이는 사용자가 이전 화면으로 돌아가지 못하게 설정할 때 유용하다.
Sage Args는 모듈 간 네비게이션을 지원하지 않는다.
그 이유는 대상 목적지로의 직접적인 액션(direct action)이 존재하지 않기 때문이다.
이전 예제에서, 설정 모듈 내의 대상 목적지에 대해 Directions 클래스가 생성되더라도, 홈 모듈의 클래스패스에서는 해당 생성된 클래스를 참조할 수 없다.
- Safe Args는 Android Navigation Component에서 제공하는 도구로, 타입 안전한 네비게이션을 지원한다.
- Safe Args는 네비게이션 그래프 내에서 액션(action)을 정의해야 작동하고, 컴파일 시점에 Directions 클래스를 자동으로 생성한다.
- Safe Args는 액션(action)을 기반으로 동작하기 때문에, 동일한 네비게이션 그래프 안에 목적지가 정의되어 있어야 한다. 하지만, 모듈 간에는 네비게이션 그래프가 분리되어 있어 직접적인 액션이 존재하지 않다.
- 따라서 Safe Args 대신 딥 링크(Deep Link)를 사용하면 모듈 간 네비게이션이 가능하다.
https://developer.android.com/guide/navigation/integrations/multi-module