본문 바로가기

Android/Tech

VersionCatalog 적용기

Unsplash, Patrick Tomasso.

동기

현재 진행 중인 네이버 부스트캠프의 학습 스프린트 기간이 종료되고 본격적으로 그룹 프로젝트 개발에 착수했습니다. CleanArchitecture 기반의 앱 서비스를 만들기 위해 모듈화를 진행했습니다.

문득 제가 개발자로 있는 프로젝트 팀의 동료분께서 VersionCatalog 적용에 대한 의사를 여러 번 표하셨던 것이 생각 나, 이번 기회에 먼저 적용해보았습니다.

 


VersionCatalog

VersionCatalog 는 의존성 선언 시 선택할 수 있는 방법 중 하나입니다.

기존 방식의 문제점

우리에게 친근한 기존의 방식은 단일 모듈 프로젝트 생성 시 사용이 간편합니다. 별도로 파일을 추가할 필요 없이, 생성되는 모듈 수준의 build.gradle 에 필요한 의존성을 추가하면 됩니다.

 

그러나, 프로젝트의 사이즈가 커지는 경우, 자연스럽게 모듈화에 대한 고민이 생기게 되고, 다중 모듈로 프로젝트가 구성되면 각 모듈에 대한 의존성 관리에 신경 써야 합니다. 이 때, 기존의 방식을 사용하는 것에는 여러 문제점이 있으며, 보통 다음과 같습니다.

  • 서로 다른 모듈에 동일한 의존성을 추가하고자 할 때, 버전을 다르게 작성할 수 있음
  • 필요한 종속성 추가를 누락할 수 있음

VersionCatalog 사용이 주는 이점

VersionCatalog 는 기존 방식의 문제점을 해결합니다. 의존성을 그룹핑하는 개념의 bundle 로 묶어 이를 프로젝트 수준에서 각 모듈의 build.gradle 에 제공할 수 있습니다. VersionCatalog 가 달성하고자 하는 것은 

의존성 데이터의 중앙 집중화이며, 개발자는 안드로이드 스튜디오의 지원을 활용해 Type-Safety 가 보장된 방식으로 이를 참조할 수 있습니다.

대략적인 사용 방법

libs.versions.toml 파일의 versions 및 libraries, plugins 섹션에 각 종속 항목을 추가합니다. 프로젝트를 동기화하고 각 모듈의 build.gradle 내 선언을 VersionCatalog 방식으로 수정합니다.

 

[versions]
# plugins
kotlin-lang = "1.9.20"
kotlin-parcelize = "1.9.10"
android-gradle-plugin = "8.1.1"

# libraries
## default
ktx = "1.12.0"
core-ktx = "1.9.0"
app-compat = "1.6.1"
lifecycle-runtime-ktx = "2.6.2"
material = "1.10.0"
lifecycle-extensions = "2.2.0"

## compose
compose = "1.8.0"
compose-ui = "1.5.4"

## test
junit = "4.13.2"
junit-ext = "1.1.5"
junit-espresso = "3.5.1"

## android-library
data-store = "1.0.0"
retrofit = "2.9.0"
okHttp = "5.0.0-alpha.2"
coil = "2.5.0"
hilt = "2.48"

[libraries]
# default
android-gradle-plugin = { group = "com.android", name = "application", version.ref = "android-gradle-plugin" }
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "ktx" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle-runtime-ktx" }
lifecycle-extensions = { group = "androidx.lifecycle", name = "lifecycle-extensions", version.ref = "lifecycle-extensions" }

# compose
activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "compose" }
compose-lifecycle = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version = "2.7.0-beta01" }
compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "compose-ui" }
compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics", version.ref = "compose-ui" }
compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview", version.ref = "compose-ui" }
compose-ui-tooling-debug = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "compose-ui" }
compose-material3 = { group = "androidx.compose.material3", name = "material3", version = "1.1.2" }
compose-hilt-navigation = { group = "androidx.hilt", name = "hilt-navigation-compose", version = "1.1.0" }

# test
junit = { group = "junit", name = "junit", version.ref = "junit" }
junit-ext = { group = "androidx.test.ext", name = "junit", version.ref = "junit-ext" }
junit-espresso = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "junit-espresso" }
compose-ui-test = { group = "androidx.compose.ui", name = "ui-test-manifest", version.ref = "compose-ui" }

# android-library
datastore = { group = "androidx.datstore", name = "data-store-preferences", version.ref = "data-store" }
datastore-core = { group = "androidx.datstore", name = "data-store-preferences-core", version.ref = "data-store" }
hilt = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }

# ksp
ksp-hilt = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" }

[bundles]
android = ["core-ktx", "lifecycle-runtime-ktx", "material", "lifecycle-extensions"]
compose = ["activity-compose", "compose-lifecycle", "compose-ui", "compose-ui-graphics", "compose-ui-tooling-preview", "compose-material3", "compose-hilt-navigation"]
common = ["hilt"]


[plugins]
android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" }
android-library = { id = "com.android.library", version.ref = "android-gradle-plugin" }
kotlin-lang = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin-lang" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
ksp = { id = "com.google.devtools.ksp", version = "1.9.0-1.0.12" }

 

실제 저희 프로젝트에 적용한 libs.versions.toml 파일입니다. 최초 적용이어서 최적인지는 좀 더 확인해보아야 하므로, 사용하는 것은 크게 추천드리지 않습니다.

 

더욱 자세한 설명은 공식 문서를 참조해주세요.

 

 

버전 카탈로그로 빌드 이전  |  Android 스튜디오  |  Android Developers

Gradle 구성 파일을 Gradle 버전 카탈로그로 이전합니다.

developer.android.com

 


그래서, 얼마나 좋아졌는가?

dependencies {
    implementation(project(":domain"))
    implementation(project(":data"))

    implementation(libs.bundles.common)

    // compose
    implementation(platform("androidx.compose:compose-bom:2023.03.00"))
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.ui:ui-graphics")
    implementation("androidx.compose.ui:ui-tooling-preview")
    implementation("androidx.compose.material3:material3")
    debugImplementation("androidx.compose.ui:ui-tooling")
    implementation(libs.lifecycle.runtime.ktx)

    // compose test
    androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
    debugImplementation("androidx.compose.ui:ui-test-manifest")
    implementation(libs.activity.compose)

    // test
    implementation(libs.junit.espresso)

}

 

libs.bundles.common 에 현재는 androidx.core:core-ktx 뿐이긴 합니다. (아마 Kotest 등의 단위테스트 라이브러리가 추가되면 변동될 것 같습니다.) bundles 에 더 많은 라이브러리가 추가되면 더욱 편리해집니다.

 

위 코드는 presentation 모듈이기 때문에 UI 관련 의존성이 추가로 필요했습니다. Jetpack Compose 의 추가로 인해, 기존의 build.gradle 과 큰 차이는 없어보이지만, Hilt 등의 전 모듈 적용 대상인 추가 라이브러리가 구성될 때마다 VersionCatalog 적용의 효과를 피부로 느낄 수 있을 것 같습니다.

 

가장 좋은 점은 Domain 레이어의 변화인데요.

 

dependencies {
    implementation(libs.bundles.common)
}

 

common Bundle 의 경우, Hilt 와 Kotest 가 추가될 예정입니다. 즉, Hilt 와 Kotest 에 대한 의존성이 VersionCatalog 에 추가되어도 Domain 모듈의 build.gradle 에는 어떠한 변화도 발생하지 않습니다.

당연히build.gradle 파일에는 변화가 없으므로 커밋에서도 제외됩니다.

 


VersionCatalog 를 적용하면 모듈이 나뉘어졌을 때 의존성 관리가 수월하다는 이야기를 꽤 많이 들어왔는데, 일단 현재는 적용만 해 둔 상태라 드라마틱한 차이는 느끼지 못하고 있습니다. 아마 앞으로 더 많이 좋아질 것 같습니다.