본문 바로가기
Develop/Android

[Android] 멀티 모듈로 프로젝트 초기세팅하기 (VersionCatalog, KSP)

by bona.com 2024. 7. 1.

이번에 리드 안드로이드 개발자로 참여한 <terning>에서 프로젝트 초기세팅을 담당하게 되었다.

그래서 나는 Clean Architecture를 기반으로 멀티 모듈을 사용해 프로젝트를 세팅하고자 했다.

그 과정을 이제부터 소개하겠다.

 

Multi Module

멀티 모듈이란, 말 그대로 하나의 애플리케이션을 여러 개의 독립적인 모듈(Module)로 나누어 개발하는 방식을 말한다.

멀티 모듈을 사용하면 각 모듈마다 기능이 독립적으로 존재하기 때문에 개발하기 용이하다. 더불어 각 모듈에 대한 의존성 분리도 명확해지기 때문에 이를 도입하고자 했다.

 

안드로이드 스튜디오에서 멀티 모듈을 만드는 방법은 그리 어렵지 않다.

아래 루트를 따라서 클릭해주면 된다.

  • app 모듈 우클릭 > New > Module

각 모듈마다 알맞는 템플릿을 선택해 준다.

  • feature (UI의 기능을 하는 모듈) : Android Library
  • domain (순수한 자바, 코틀린 코드로 이루어진 모듈) : Java or Kotlin Library
  • data (비즈니스 로직을 작성하는 모듈) : Android Library

 

각 모듈의 의존성을 본인이 구상한 바에 맞게 연결해 준다.

dependencies {
    implementation(project(":core"))
    implementation(project(":data"))
    implementation(project(":domain"))
    implementation(project(":feature"))
}

위는 app 모듈의 의존성이다. 

app 모듈의 경우 모든 모듈을 알고 있어야 하므로 내가 생성한 모든 모듈을 넣어주었다.

terning 모듈 의존성 그래프

terning의 경우 모듈 의존성을 그래프로 나타내면 이와 같다.

 

마지막으로 각 모듈에 맞는 그래들 파일을 작성해주면 된다.

app 모듈의 그래들 파일만 예시로 가지고 와봤다.

plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.hilt)
    alias(libs.plugins.kotlin.parcelize)
    alias(libs.plugins.ksp)
}

android {
    namespace = "com.terning.point"
    compileSdk = libs.versions.compileSdk.get().toInt()

    defaultConfig {
        applicationId = "com.terning.point"
        minSdk = libs.versions.minSdk.get().toInt()
        targetSdk = libs.versions.targetSdk.get().toInt()
        versionCode = libs.versions.versionCode.get().toInt()
        versionName = libs.versions.versionName.get()

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary = true
        }

        buildConfigField(
            "String",
            "NATIVE_APP_KEY",
            gradleLocalProperties(rootDir, providers).getProperty("native.app.key"),
        )
        manifestPlaceholders["NATIVE_APP_KEY"] =
            gradleLocalProperties(rootDir, providers).getProperty("nativeAppKey")
    }

    buildTypes {
        debug {
            buildConfigField(
                "String",
                "BASE_URL",
                gradleLocalProperties(rootDir, providers).getProperty("base.url")
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = libs.versions.jvmTarget.get()
    }
    buildFeatures {
        compose = true
        buildConfig = true
    }
    composeOptions {
        kotlinCompilerExtensionVersion = libs.versions.kotlinCompilerExtensionVersion.get()
    }
    packaging {
        resources {
            excludes += "/META-INF/{AL2.0,LGPL2.1}"
            excludes += "/META-INF/gradle/incremental.annotation.processors"
        }
    }
}

 

VersionCatalog

위 각 모듈의 그래들 코드를 보면 Gradle Version Catalog 방식을 사용한 흔적을 찾을 수 있을 것이다.

version catalog는 의존성과 버전을 한 곳에서 관리할 수 있도록 도와주는 기능이다.

이는 libs.versions.toml 파일에서 작성해 주면 된다.

 

총 세 가지 카테고리로 이루어져 있다.

  • 1. versions
    • libraries와 plugins에서 사용되는 버전 정보를 담는다.
[versions]
lottieVersion = "6.0.0"

 

  • 2. libraries
    • 의존성 정보를 담는다.
    • 기존의 라이브러리에서 :을 통해 구분하여 group : name : version.ref 로 나타낸다.
    • 그래들에서 사용할 때는 아래처럼 사용해주면 된다.
[libraries]
lottie = {group = "com.airbnb.android", name = "lottie-compose", version.ref = "lottieVersion"}
dependencies {
	implementation(libs.lottie)
}

 

  • 3. plugins
    • 플러그인 정보를 담는다.
    • 그래들에서 사용할 때는 아래처럼 사용해주면 된다.
[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
plugins {
    alias(libs.plugins.android.library)
}

 

KAPT -> KSP 마이그레이션

  • KAPT는 java로 작성되어 있지만, KSP는 kotlin을 기반으로 작성되어 있기 때문에 효율적이며, 더 빠른 컴파일 타임을 제공한다.

KSP를 사용하기 위해선 먼저 프로젝트 단의 그래들에 명시해준다.

plugins {
    alias(libs.plugins.ksp) apply false
}

 

이때 버전 카탈로그에는 다음과 같이 작성이 되어 있다.

ksp = "1.9.0-1.0.12"

[plugins]
ksp = {id = "com.google.devtools.ksp", version.ref = "ksp"}

 

그리고 모듈 단의 그래들에 아래처럼 작성해주면 된다.

plugins {
    alias(libs.plugins.ksp)
}

 

트러블슈팅 기록

컴포즈로 멀티모듈을 생성하다 보면 아래와 같은 오류 메세지를 볼 수 있을 것이다.

Execution failed for task ':feature:mergeDebugAndroidTestJavaResource'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction
   > 2 files found with path 'META-INF/gradle/incremental.annotation.processors' from inputs:

 

이 에러는 두 개의 라이브러리(hilt-compiler와 dagger-compiler)가 동일한 리소스 파일을 포함하고 있어서 발생한 것이다.

이를 해결하기 위해 build.gradle 파일의 android 블록에 packagingOptions를 추가하여 충돌을 해결하였다.

android {
    packagingOptions {
        resources {
            excludes += 'META-INF/gradle/incremental.annotation.processors'
        }
    }
}

 

마무리

프로젝트 초기세팅을 하는 것은 안드로이드에 대한 기본 지식을 다질 수 있는 좋은 기회라고 생각한다.

앱이 어떻게 이루어져 있는지, 어떤 방식으로 구축해야 효율적인지를 생각하며 개발하니 아키텍처에 대한 지식을 얻어갈 수 있었다.

 

내가 프로젝트 초기세팅을 한 과정이 담긴 PR 링크도 남기도록 하겠다.

📍프로젝트 초기세팅 PR: https://github.com/teamterning/Terning-Android/pull/2

 

[ADD/#1] 프로젝트 초기 세팅 by leeeyubin · Pull Request #2 · teamterning/Terning-Android

closed [ADD] 프로젝트 초기 세팅 #1 ⛳️ Work Description gradle 기초 세팅 manifest 기초 세팅 naviagtion 세팅 모듈 세팅 myApp 추가 확장함수 추가 디렉토리 별 dummy 파일 만들어두기 themes 기초 세팅 타이포

github.com