[Android, Multi Module] 멀티 모듈을 위한 구조 설계

다중 모듈 탐색 권장사항

유연성을 통해 탐색 그래프를 결합하여 앱의 완전한 탐색 그래프를 구성할 수 있다.

- 단일 대상 활용 (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

 

NavigationUI  |  Android Developers

androidx.core.performance

developer.android.com

 

위 클래스에는 상단 앱 바, 탐색 창 및 하단 탐색으로 탐색을 관리하는 정적 메서드가 포함되어 있다.

앱 모듈은 공동작업 기능 모듈에 종속되므로 앱 모듈 내에 정의된 코드에서 모든 대상에 엑세스할 수 있다.

 

앱의 최상위 대상이 기능 모듈에서 제공하는 UI 요소로 구성되어 있다면,

app 모듈이  최상위 탐색 및 UI 요소를 배치하기에 적합한 위치이며, 모든 대상에 엑세스 할 수 있다.

( 공동작업 기능 모듈에 종속되므로 앱 모듈 내에 정의된 모든 코드에서 기능 모듈에 접근 가능)

즉 항목 ID가 대상 ID와 일치하면 NavigationUI를 사용해서 대상을 메뉴 항목에 연결할 수 있다.

 

NavigationUI 지원 

https://developer.android.com/guide/navigation/navigation-ui?hl=ko#Tie-navdrawer 

 

NavigationUI로 UI 구성요소 업데이트  |  Android 개발자  |  Android Developers

NavigationUI로 UI 구성요소 업데이트 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 탐색 구성요소에는 NavigationUI 클래스가 포함되어 있습니다. 이 클래스에는

developer.android.com

 

위에서 기능 모듈을 분리한 아키텍처를 참고하면, 아래와 같은 뷰를 구성할 수 있다.

메뉴의 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)
}

https://developer.android.com/reference/androidx/navigation/ui/NavigationUI#setupWithNavController(android.support.design.widget.NavigationView,%20androidx.navigation.NavController) 

 

NavigationUI  |  Android Developers

androidx.core.performance

developer.android.com

 

사용자가 하단 탐색 항목을 클릭했을 때, 적절한 라이브러리 그래프로 이동하게 된다.

대부분의 경우 개발자는 삽입 또는 포함된 탐색 그래프의 진입점에 대해서만 앱 모듈이 알기 바라며,

그래프 내 깊이 삽입된 특정 대상에 많은 제약 하에 종속되는 것은 좋지 않다.

 

깊은 대상 연결을 원한다면 딥 링크를 활용하는 방법도 있다.

 

기능 모듈의 탐색

컴파일 시 독립된 기능 모듈은 서로 확인할 수 잆으므로 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로도 이동할 수 있고, 모듈 간 이동도 가능하다.

 

멀티 모듈 구성하기

 

멀티모듈을 구성할 때, 아래와 같은 레이어 구상을 할 수 있다.

https://www.youtube.com/watch?v=nH382BcycHc

내부 모듈 계층 : 시스템 안에서 의미를 가지고 애플리케이션, 도메인 비즈니스의 로직은 모르는 계층

- 환경별 시스템 Host, Header를 관리하고 요청, 응답을 관리하고 예외 처리에 대한 수준을 통일하는 모듈

 

도메인 모듈 계층 : 같은 도메인에 다른 인프라를 사용하는 경우가 존재할 수 있으며, 이를 분리하는 작업을 수행

- 인프라스트럭처를 분리하지 않으면 거대해지는 common과 같은 부작용이 존재할 수 있음

- 모듈을 따로 정리하여 의존성을 분리 (cache, Fallback에 대한 작업 처리)

- 일반적으로 Domain Service를 domain-redis, domain-dynamo로 분리하여 도메인 서버 모듈을 분리

 

독립 모듈 계층 : 시스템과 관련 없이 자체로서 독립적인 역할을 수행

 

공통 모듈 계층 : Type, Util 등을 정의하며 의존성을 전혀 가지지 않고 시간 범위 등을 관리

애플리케이션 모듈 계층 : 하위 모듈을 조립하는 작업이 수행

 

이러한 아키텍처로 모듈을 분리하면, 아래와 같은 구조를 가질 수 있다.

https://www.youtube.com/watch?v=nH382BcycHc