Idealim
article thumbnail

참고 자료

[Github - ComposeCookBook] : https://github.com/Gurupreet/ComposeCookBook


#Kotlin DSL

Github에서 JetPack ComposeBook 오픈소스를 분석하던 중, buildSrc를 통해 dependency를 관리하는 방법을 알게 되었다. 

많은 gradle..

이 프로젝트에서는 많은 모듈로 이루어져 있는데 모듈의 build.gradle을 kotlin-dsl 을 사용하여 혁신(?)적으로 관리한다. (모듈이 많으면 gradle이 많아지는 단점이 있다.) 


관련글

[우아한 형제들 기술 블로그Gradel Kotlin DSL 이야기 ] : https://techblog.woowahan.com/2625/


위 글을 통해 kotlin-dsl 에 대해 알아보았다. 개인적으로 배운 점&기억해야될 것들을 요약해보았다.

멀티 프로젝트(모듈)
프로젝트를 구성하다보면 공통된 기능과 코드를 하나의 모듈로 몰아놓고 역할에 따라서 각기 다른 모듈에서 참조하여 사용하는 방식을 사용한다. 
하위 프로젝트에 대한 정의를 최상위 프로젝트에서 allprojects , subprojects 속성을 통해 각 프로젝트의 공통 속성과 작동을 정의할 수 있다. (모듈에 대해 좀 더 알아봐야 겠다..)

Gradle Kotlin DSL

내가 기존에 사용하고 있던 dsl은 확장자명이 .gradle 로 끝나는 Groovy DSL 이다. (DSL 이란 특정 분야에 최적화된 프로그래밍 언어를 뜻한다.) Groovy DSL 과 Kotlin DSL 을 비교해보자.

Groovy DSL / Kotlin DSL 비교

plugins {
    id 'com.android.application'
    id 'kotlin-android'
}

android {
    compileSdk 31
    buildToolsVersion "31.0.0"

    defaultConfig {
        applicationId "com.example.composeuibasic"
        minSdk 21
        targetSdk 31
        versionCode 1
        versionName "1.0"

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

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
        useIR = true
    }
    buildFeatures {
        compose true
    }
    composeOptions {
        kotlinCompilerExtensionVersion compose_version
        kotlinCompilerVersion '1.5.10'
    }
}

 

Groovy DSL Dependencies

Kotlin DSL

import com.guru.composecookbook.build.configurations.ProjectConfigs
import com.guru.composecookbook.build.dependencies.*

plugins {
    id("com.android.application")
    id("kotlin-android")
    id("kotlin-kapt")
}

android {

    compileSdk = ProjectConfigs.compileSdkVersion

    defaultConfig {
        applicationId = ProjectConfigs.applicationId
        minSdk = ProjectConfigs.minSdkVersion
        targetSdk = ProjectConfigs.targetSdkVersion
        versionCode = 1
        versionName = "1.0"
        multiDexEnabled = true
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
        useIR = true
    }
    buildFeatures {
        compose = true
    }
    composeOptions {
        kotlinCompilerExtensionVersion = ProjectConfigs.kotlinCompilerExtensionVersion
    }

}
Kotlin DSL Dependencies

코틀린 DSL 은 모든 문자열을 큰 따옴표(")로 작성한다. (코틀린 문법을 따르므로 문자열은 " " 로 묶어서 표현함.)

import를 사용가능하다. 


Kotlin DSL 의 장점

Kotlin DSL 은 코틀린 언어적 특징인 가독성이 좋고 간략한 코드를 사용하여 Gralde 스크립팅을 하는 것을 목적으로 한다. 이를 통해 얻을 수 있는 이점은 다음과 같다.

  • 코드 자동완성 사용 가능
  • 컴파일 타임에 에러 확인
  • 코드 탐색 
  • 구문 강조
  • 소스코드와 동일한 언어의 사용

Kotlin DSL 의 단점

당연히 단점도 존재한다. 

https://www.charlezz.com/?p=45140
내 수준에서 단점을 이해하자면 바로 느려진다는 것이다. (아직 빌드에 대한 지식이 없어서 abi change가 먼지 잘 모르겠다.. 빌드에 대해 공부해야겠다.)

Kotlin DSL 사용

1. Groovy DSL -> Kotlin DSL 변경

변경하는 법은 간단하다. 기존 .gradle 파일을 *.gradle.kts로 변경하면 된다. 

예) build.gradle -> build.gradle.kts

2. buildSrc로 버전관리 하기

관련글

[Android Studio의 build.gradle 자동 완성을 위한 Kotlin 및 buildSrc를 사용한 Gradel 종속성 관리] :

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

buildSrc를 만드는 방법은 위 유튜브 동영상을 보면 이해할 수 있다.

추가적으로 build 캐시를 git에서 관리하지 않도록 .gitignore에 buildSrc/build 를 추가한다.


ComposeBook Kotlin DSL 사용법 분석

1. ProjectConfigs

sdk 에 대한 정보 / 앱 ID / kotlin 컴파일러 버전을 관리하는 object이다.
object ProjectConfigs {
    const val compileSdkVersion = 30
    const val minSdkVersion = 21
    const val targetSdkVersion = 30
    const val applicationId = "com.guru.composecookbook"
    const val kotlinCompilerExtensionVersion = Versions.compose
}
2. Dependencies
의존성 String 값을 상수에 담는 object이다.
object Dependencies {
    const val composeUi = "androidx.compose.ui:ui:${Versions.compose}"
    const val composeUiTooling = "androidx.compose.ui:ui-tooling:${Versions.compose}"
    const val composeMaterial = "androidx.compose.material:material:${Versions.compose}"
    const val composeMaterialIconsExtended =
        "androidx.compose.material:material-icons-extended:${Versions.compose}"
    const val composeRuntimeLivedata =
        "androidx.compose.runtime:runtime-livedata:${Versions.compose}"

// 생략..
3. GroupDependencies
관련 의존성을 묶은 리스트 변수들을 관리하는 파일이다.
internal val composeOfficialDependencies = listOf(
    Dependencies.composeUi,
    Dependencies.composeUiTooling,
    Dependencies.composeMaterial,
    Dependencies.composeMaterialIconsExtended,
    Dependencies.composeRuntimeLivedata,
    Dependencies.composeConstraintLayout,
    Dependencies.composePaging,
    Dependencies.composeViewModel,
    Dependencies.composeActivity,
    Dependencies.composeNavigation,
)
 

4. DependencyHandlerExtensions.kt

DependencyHandler 의 확장함수를 정의하는 부분이다. GroupDependencies의 리스트 변수들에 implementaion 을 붙여준다. build.gradle.ktx 에서 함수처럼 사용할 수 있다.

import org.gradle.api.artifacts.dsl.DependencyHandler

fun DependencyHandler.addComposeOfficialDependencies() {
    composeOfficialDependencies.forEach {
        add("implementation", it)
    }
}

Module plugin 관리

plugins {
    /**
     * See [common-compose-module-configs-script-plugin.gradle.kts] file
     */
    id("common-compose-module-configs-script-plugin")
}

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

    addComposeOfficialDependencies()
}

 위 코드처럼 모듈의 id("common-compose-module-configs-script-plugin") 를 사용하여 plugin , kotlin android, android {} 블록을 불러와보자.

1. buildSrc의 build.gradle.kts 에 다음 의존성 추가

dependencies {
    // in order to be able to use "kotlin-android" in the common script
    implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10")

    // in order to recognize the "plugins" block in the common script
    implementation("com.android.tools.build:gradle:7.1.0-alpha02")

    // in order to recognize the "android" block in the common script
    implementation("com.android.tools.build:gradle-api:7.1.0-alpha02")
}

2. buildSrc 폴더에 file 추가 common-compose-module-configs-script-plugin.gradle.kts / common-kotlin-module-configs-script-plugin.gradle.kts 추가 (두 파일의 차이는 Compose 전용 컴파일러 설정)

//compose module configs script
plugins {
    id("com.android.library")
    id("kotlin-android")
}

android {
    compileSdk = ProjectConfigs.compileSdkVersion

    defaultConfig {
        minSdk = ProjectConfigs.minSdkVersion
        targetSdk = ProjectConfigs.targetSdkVersion
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
        freeCompilerArgs = freeCompilerArgs + "-Xopt-in=kotlin.RequiresOptIn"
    }
    buildFeatures {
        compose = true
    }
    composeOptions {
        kotlinCompilerExtensionVersion = ProjectConfigs.kotlinCompilerExtensionVersion
    }
}

// kotlin-module-configs-script
plugins {
    id("com.android.library")
    id("kotlin-android")
    id("kotlin-kapt")
}

android {
    compileSdk = ProjectConfigs.compileSdkVersion

    defaultConfig {
        minSdk = ProjectConfigs.minSdkVersion
        targetSdk = ProjectConfigs.targetSdkVersion
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

이제 plugins 에 id("common-compose-module-configs-script-plugin") 를 추가하면 첫 번째 compose script가 입력된다. 


 
 

Kotlin DSL 을 사용하면 모듈들의 gradle 과 버전을 쉽게 관리할 수 있다는 것을 배웠다. 

이번 기회를 통해 클린 아키텍처에 대해 관심이 생겼다.

핵심은 '어떻게 하면 좀 더 쉽게 유지보수를 하고 구조화할 수 있을까?' 인 거 같다.

작은 토이프로젝트에서는 이런 구조들이 필요 없을 가능성이 높지만, 앞으로 내가 만들 서비스들을 계속해서 발전시키려면 이런 구조들을 적용시키는 것이 좋을 거 같다. 

클린 아키텍처 / 모듈에 대해 더 공부해 나가자.

반응형
profile

Idealim

@Idealim

읽어주셔서 감사합니다. 잘못된 내용이 있으면 언제든 댓글로 피드백 부탁드립니다.