다중 모듈 탐색 권장사항
유연성을 통해 탐색 그래프를 결합하여 앱의 완전한 탐색 그래프를 구성할 수 있다.
- 단일 대상 활용 (fragment)
- 일련의 관련 대상을 캡슐화하는 중첩 그래프
- 중첩된 것처럼 다른 탐색 그래프 파일을 삽입할 수 있는 <include> 요소 활용
아래 예는 각 기능 모듈이 한 기능에 중점을 두고 이 기능을 구현하는 데 필요한 모든 대상을 캡슐화하는 단일 탐색 그래프를 제공한다.
프로덕션 앱에는 이 상위 수준 기능 모듈의 구현 세부정보인 하위 모듈이 하위 수준에 여러 개 있을 수 있다.
이러한 모듈은 간접적으로 App 모듈에 포함되어 진다.
각 기능 모듈은 자체 탐색 그래프와 대상이 있는 독립된 단위이다. (내비게이션 메뉴)
app 모듈은 각각에 종속되므로 아래와 같이 build.gradle 파일에 구현 세부정보를 추가한다.
dependencies {
...
implementation(project(":feature:home"))
implementation(project(":feature:favorites"))
implementation(project(":feature:settings"))
이를 통해서 app에서는 종속 된 모듈의 기능을 가져와서 사용할 수 있다.
app 모듈의 역할
app 모듈은 앱의 완전한 그래프를 제공하고 NavHost를 UI에 추가한다.
app 모듈의 탐색 그래프 내에서는 <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>
위 코드에서는 include를 통해서 종속 모듈의 라이브러리를 참고하고 있다.
<include> 태그의 그래프 속성은 라이브러리의 그래프 파일 이름을 참조한다. (ex> home_navigation.xml)
startDestination은 이 파일 내 <navigation> 요소의 ID를 참조하며, @+id를 사용하지 않는 것이 특징이다.
대신 @id/를 활용해서 이미 기능 모듈에서 선언된 ID를 사용한다.
앱 모듈의 최상위 탐색
탐색 구성요소에는 NavigationUI 클래스가 포함되어 있다.
https://developer.android.com/reference/androidx/navigation/ui/NavigationUI
위 클래스에는 상단 앱 바, 탐색 창 및 하단 탐색으로 탐색을 관리하는 정적 메서드가 포함되어 있다.
앱 모듈은 공동작업 기능 모듈에 종속되므로 앱 모듈 내에 정의된 코드에서 모든 대상에 엑세스할 수 있다.
앱의 최상위 대상이 기능 모듈에서 제공하는 UI 요소로 구성되어 있다면,
app 모듈이 최상위 탐색 및 UI 요소를 배치하기에 적합한 위치이며, 모든 대상에 엑세스 할 수 있다.
( 공동작업 기능 모듈에 종속되므로 앱 모듈 내에 정의된 모든 코드에서 기능 모듈에 접근 가능)
즉 항목 ID가 대상 ID와 일치하면 NavigationUI를 사용해서 대상을 메뉴 항목에 연결할 수 있다.
NavigationUI 지원
https://developer.android.com/guide/navigation/navigation-ui?hl=ko#Tie-navdrawer
위에서 기능 모듈을 분리한 아키텍처를 참고하면, 아래와 같은 뷰를 구성할 수 있다.
메뉴의 item은 다른 모듈의 메인 뷰롤 참조하는 기능을 한다.
<?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()를 호출하게 하면 된다.
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)
}
사용자가 하단 탐색 항목을 클릭했을 때, 적절한 라이브러리 그래프로 이동하게 된다.
대부분의 경우 개발자는 삽입 또는 포함된 탐색 그래프의 진입점에 대해서만 앱 모듈이 알기 바라며,
그래프 내 깊이 삽입된 특정 대상에 많은 제약 하에 종속되는 것은 좋지 않다.
깊은 대상 연결을 원한다면 딥 링크를 활용하는 방법도 있다.
기능 모듈의 탐색
컴파일 시 독립된 기능 모듈은 서로 확인할 수 잆으므로 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>
위와 같이, 설정 탐색 그래프에서 대상에 딥 링크를 추가하기만 하면 된다.
이 후에 홈 프래그먼트의 버튼의 onClcikListener에 다음 코드를 추가한다면, 다른 모듈로의 이동을 할 수 있다.
button.setOnClickListener {
val request = NavDeepLinkRequest.Builder
.fromUri("android-app://example.google.app/settings_fragment_two".toUri())
.build()
findNavController().navigate(request)
}
작업 또는 대상 ID를 사용하는 탐색과는 다르게, 모든 그래프의 어떤 URI로도 이동할 수 있고, 모듈 간 이동도 가능하다.
멀티 모듈 구성하기
멀티모듈을 구성할 때, 아래와 같은 레이어 구상을 할 수 있다.
내부 모듈 계층 : 시스템 안에서 의미를 가지고 애플리케이션, 도메인 비즈니스의 로직은 모르는 계층
- 환경별 시스템 Host, Header를 관리하고 요청, 응답을 관리하고 예외 처리에 대한 수준을 통일하는 모듈
도메인 모듈 계층 : 같은 도메인에 다른 인프라를 사용하는 경우가 존재할 수 있으며, 이를 분리하는 작업을 수행
- 인프라스트럭처를 분리하지 않으면 거대해지는 common과 같은 부작용이 존재할 수 있음
- 모듈을 따로 정리하여 의존성을 분리 (cache, Fallback에 대한 작업 처리)
- 일반적으로 Domain Service를 domain-redis, domain-dynamo로 분리하여 도메인 서버 모듈을 분리
독립 모듈 계층 : 시스템과 관련 없이 자체로서 독립적인 역할을 수행
공통 모듈 계층 : Type, Util 등을 정의하며 의존성을 전혀 가지지 않고 시간 범위 등을 관리
애플리케이션 모듈 계층 : 하위 모듈을 조립하는 작업이 수행
이러한 아키텍처로 모듈을 분리하면, 아래와 같은 구조를 가질 수 있다.