Android Version Catalog란?

2023. 7. 31. 20:05android

 

Android Version Catalog란?

 

Android Version Catalog란 android build gradle의 version 관리 방식 중에 하나이다.

 

 

왜 버전 관리를 하고 왜 이런 걸 만들었을까?

 

 프로젝트의 규모가 커지면 그에 따른 다양한 문제들을 마주한다. 모놀리틱 한 개발은 기능이 추가되면 점점 스파게티 코드가 되고 견고한 아키텍처가 아니면 유지 보수에 더 많이 시간을 투자하게 된다. 이거야말로 배보다 배꼽이 더 큰 상황이 와버린다. 이러한 문제들을 해결하기 위해 모듈화의 진행은 필수이다.

 

 모듈화 Moduralization을 통해 하나의 프로젝트를 다양한 모듈로 구성하여 분리도, 독립성을 높이고 로직을 분리하여 재사용성을 높이면 결과적으로 경제적인 이득을 가져온다. 이러한 모듈화를 진행하면 당연하게도 하나의 모듈을 추가하기 위해 여러 Cost가 존재하고 그중에 하나 기존의 dependency를 확인하고 연결시켜야 한다. 분리도가 높아진다는 건 그만큼 복잡도가 증가할 수 있다는 말이기 때문에 모듈들의 빌드 관리를 어떻게 하면 통합, 확장하여 관리성을 높일 수 있을까 필요에 의해 만들어졌다고 생각한다. 모듈을 추가할 때마다 필요한 dependency를 계속 입력하고 찾아보고 다했다 생각에 모듈 추가하고 빌드하니 뭔가 오류가 나고 보니까 버전이 안 맞아서 오류가 발생하고 그럼 또 찾아서 가능한 버전으로 낮추고.. 진짜 빌드 세팅하다 시간 날리는 이런 번거롭고 짜증 나는 상황을 최대한 감소하여 일의 효율을 높일 수 있게 된다.  

 

 

대표적인 Android gradle version 관리 방식에는 무엇이 있을까?

 

  1. ext
  2. BuildSrc
  3. Version Catalog

 

Version Catalog의 장점은 무엇인가?

 

  • Version 관리의 궁극적인 목적 = 관리의 용이함
    How? 하나의 파일에 버전 정보를 적고 Kotlin DSL을 통해 자동완성을 함께 사용하여 간편함과 관리의 용이함을 함께 잡는다.

 

  • 잘 정리하기만 하면 Dependency의 구분과 집합을 나눌 수 있다.
    But! 변수명처럼 Dependency의 그룹과 이름을 정하기 때문에 질서와 규약이 없다면 오히려 독이 될 수 있다. 따라서 체계적으로 나누고 추가, 수정된 결과는 문서화하자!

 

 

 

어떻게 적용하고 사용하나?

 

Version Catalog는 AGP 7.0 이상에서 적용 가능. 그리고 7.4 아래는 조금 다르다. 

그리고 Android Giraffe Version이 Stable로 출시되었다. Giraffe에서 새 프로젝트를 만들게 되면 아래와 같이 기본적으로 Compose와 Kotlin DSL을 Recommended 하고 있다. 자세한 사항은 아래의 링크에서 확인 가능하다.

 

https://android-developers.googleblog.com/2023/07/android-studio-giraffe-is-stable.html

 

Android Studio Giraffe is stable

we are thrilled to announce the stable release of Android Studio Giraffe. Download now to incorporate the new features into your workflow.

android-developers.googleblog.com

 

Graffe version을 사용한다면 기본적으로 바로 생성할 수 있다.

 

 

 

 

 

 

오른쪽 사진의 Configuration을 누르면 드롭다운 메뉴에 Experimental 하다는 설명과 Gradle Version Catalog를 자동으로 생성해서 그에 맞게 프로젝트를 생성한다. 

 

 

 

기본 프로젝트에 Version Catalog가 생성됨을 확인할 수 있다. 

 

  • [versions]  
  • [libraries]
  • [plugins]
  • [bundles]

 

 

4가지 항목을 적어서 적용한다.  versions에는 version 정보들을 적고 libraries에 프로젝트에 적용할 라이브러리들을 적는다. plugins에는 모듈의 plugins에 필요한 항목을 적고 Bundle은 라이브러리 묶음이라고 생각하면 된다.

 

 

 

 

사진을 보면 plugins에 예전에 id라고 되어있는 의존성들이 alias로 변경되었다. 

 

Q. 만약 기존 프로젝트를 migration 하고 싶다면?

 

1 ) toml 파일을 만든다.

먼저 프로젝트 디렉터리 보기를 Project로 변경하고 gradle 폴더 안에 libs.versions.toml을 만든다.

 

 

 

이 안에 이제 4개의 항목을 일단 넣는다.

 

  • [versions]  
  • [libraries]
  • [plugins]
  • [bundles]

 

 

이제 자신의 프로젝트에서 사용하는 dependency를 확인하고 필요한 버전들을 전부 입력한다.

 

 

 

더보기
[versions]
agp = "8.1.0"
org-jetbrains-kotlin-android = "1.8.10"
kotlin-kapt = "1.8.10"
kotlin-ksp = "1.8.10-1.0.9"
kotlin-parcelize = "1.8.10"
core-ktx = "1.10.1"
lifecycle-runtime-ktx = "2.6.1"
activity-compose = "1.7.2"
compose-bom = "2023.03.00"
compose-runtime = "1.4.3"
navigation-compose = "2.6.0"
hilt = "2.45"
hilt-navigation-compose = "1.0.0"
camera = "1.3.0-beta01"
room = "2.5.2"
coroutine = "1.6.4"
capturable = "1.0.3"
junit = "4.13.2"
androidx-test-ext-junit = "1.1.5"
espresso-core = "3.5.1"
test-core = "1.5.0"
mockk = "1.13.5"

[libraries]
# = {group = "", name = "", version.ref = ""}
androidx-core-ktx = { module = "androidx.core:core-ktx", version = "1.10.1" }
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" }
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
ui = { group = "androidx.compose.ui", name = "ui" }
ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
material3 = { group = "androidx.compose.material3", name = "material3" }
material3-window-size = {group = "androidx.compose.material3", name = "material3-window-size-class"}
camera-core = {group = "androidx.camera", name = "camera-core", version.ref = "camera"}
camera-camera2 = {group = "androidx.camera", name = "camera-camera2", version.ref = "camera"}
camera-view = {group = "androidx.camera", name = "camera-view", version.ref = "camera"}
camera-lifecycle = {group = "androidx.camera", name = "camera-lifecycle", version.ref = "camera"}
camera-extensions = {group = "androidx.camera", name = "camera-extensions", version.ref = "camera"}
navigation-compose = {group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation-compose"}
runtime = {group = "androidx.compose.runtime", name = "runtime", version.ref = "compose-runtime"}
lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle-runtime-ktx" }
room-runtime = {group = "androidx.room", name = "room-runtime", version.ref = "room"}
room-ktx = {group = "androidx.room", name = "room-ktx", version.ref = "room"}
room-compiler = {group = "androidx.room", name = "room-compiler", version.ref = "room"}
room-testing = {group = "androidx.room", name = "room-testing", version.ref = "room"}
coroutine = {group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutine"}
coroutine-test = {group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutine"}
test-core = {group = "androidx.test", name = "core-ktx", version.ref = "test-core"}
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" }
espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" }
ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
hilt-android = {group = "com.google.dagger", name = "hilt-android", version.ref = "hilt"}
hilt-compiler = {group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt"}
hilt-navigation-compose = {group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hilt-navigation-compose"}
capturable = {group = "dev.shreyaspatil", name = "capturable", version.ref = "capturable"}
mockk = {group = "io.mockk", name = "mockk", version.ref = "mockk"}
mockk-android= {group = "io.mockk", name = "mockk-android", version.ref = "mockk"}

[plugins]
com-android-application = { id = "com.android.application", version.ref = "agp" }
org-jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "org-jetbrains-kotlin-android" }
com-google-dagger-hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt"}
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin-parcelize" }
kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin-kapt" }
kotlin-ksp = { id = "com.google.devtools.ksp", version.ref = "kotlin-ksp" }

[bundles]

 

 

적는 방식은 두 가지가 있다. 

 

androidx-core-ktx = { module = "androidx.core:core-ktx", version = "1.10.1" }
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }

 

둘 다 같은 core-ktx 라이브러리를 가져온다. 

두 번째 방식은 : 를 기준으로 앞이 group이고 뒤가 name이다.

 

이렇게 전부 적어주면 된다. 다 적으면 gradle sync.

 

만약 7.4 아래 버전을 사용하시면 아래와 같이 적어야 합니다.

 

// Gradle 7.4 미만일 경우
// settings.gradle.kts

enableFeaturePreview("VERSION_CATALOGS")

dependencyResolutionManagement {
    versionCatalogs {
        create("libs") {
            from(files("libs.versions.toml"))
        }
    }
}

 

2. 적용하기

 

1 )  Top Level build.gradle.kts

 

// Top-level build file where you can add configuration options common to all sub-projects/modules.
@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
plugins {
    alias(libs.plugins.com.android.application) apply false
    alias(libs.plugins.org.jetbrains.kotlin.android) apply false
    alias(libs.plugins.com.google.dagger.hilt) apply false
    alias(libs.plugins.kotlin.parcelize) apply false
    alias(libs.plugins.kotlin.kapt) apply false
    alias(libs.plugins.kotlin.ksp) apply false
}
true // Needed to make the Suppress annotation work for the plugins block

 

 

2 )  App Module build.gradle.kts

 

@Suppress("DSL_SCOPE_VIOLATION")
plugins {
    alias(libs.plugins.com.android.application)
    alias(libs.plugins.org.jetbrains.kotlin.android)
    alias(libs.plugins.com.google.dagger.hilt)
    alias(libs.plugins.kotlin.parcelize)
    alias(libs.plugins.kotlin.ksp)
    alias(libs.plugins.kotlin.kapt)
}

android {
    namespace = "--------"
    compileSdk = 33

    defaultConfig {
        applicationId = "---------"
        minSdk = 26
        targetSdk = 33
        versionCode = 1
        versionName = "1.0"

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

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_--
        targetCompatibility = JavaVersion.VERSION_--
    }
    kotlinOptions {
        jvmTarget = "--"
    }
    buildFeatures {
        compose = true
    }
    composeOptions {
        kotlinCompilerExtensionVersion = "1.4.3"
    }
    packaging {
        resources {
            excludes += "/META-INF/{AL2.0,LGPL2.1}"
        }
    }
}

dependencies {

    implementation(libs.core.ktx)
    implementation(libs.lifecycle.runtime.ktx)
    implementation(libs.activity.compose)
    implementation(platform(libs.compose.bom))
    implementation(libs.ui)
    implementation(libs.ui.tooling.preview)
    implementation(libs.material3)
    implementation(libs.material3.window.size)
    implementation(libs.camera.core)
    implementation(libs.camera.camera2)
    implementation(libs.camera.view)
    implementation(libs.camera.lifecycle)
    implementation(libs.camera.extensions)
    implementation(libs.navigation.compose)
    implementation(libs.runtime)
    implementation(libs.hilt.android)
    implementation(libs.hilt.navigation.compose)
    implementation(libs.capturable)
    implementation(libs.room.runtime)
    implementation(libs.room.ktx)
    implementation(libs.coroutine)
    implementation(libs.test.core)
    testImplementation(libs.mockk)
    androidTestImplementation(libs.mockk.android)
    testImplementation(libs.coroutine.test)
    testImplementation(libs.room.testing)
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.test.ext.junit)
    androidTestImplementation(libs.espresso.core)
    androidTestImplementation(platform(libs.compose.bom))
    androidTestImplementation(libs.ui.test.junit4)
    debugImplementation(libs.ui.tooling)
    debugImplementation(libs.ui.test.manifest)
    kapt(libs.hilt.compiler)
    ksp(libs.room.compiler)

}

 

위와 같이 똑같이 적을 필요는 없습니다. 저의 실제 프로젝트를 migration 하면서 적는 글이라 필요한 부분만 보시면 됩니다.  중요한 점은 module의 plugins에 적는다면 project plugins에도 동일하게 적어야 합니다. 꼭.

 

migration 규칙들은 보시면 금방 아실 겁니다. 자세한 사항이 궁금하시면 아래에 링크에서 확인하시면 됩니다.

https://developer.android.com/build/migrate-to-catalogs#kts

 

Migrate your build to version catalogs  |  Android Studio  |  Android Developers

Migrate your Gradle configuration files to Gradle version catalogs.

developer.android.com