본문 바로가기

Android 공부/KMM

코틀린 밖에 못하는 바보 ... - Compose multiplatform & Kotlin multiplatform 경험기 (1)

해지호그 프로젝트

https://superlative-khapse-63fb9a.netlify.app

 

왁자지껄 해지호그

을지로 야장 전문 와인바, HEDGEHOG로 이동합니다.

superlative-khapse-63fb9a.netlify.app

 여름이 서서히 다가올 6월 쯤에, 친구는 을지로에 와인바를 차린다고 했다. 친한 친구가 창업을 한다고 하니, 나도 뭔가 도움을 주고 싶었다. 아직 정식으로 영업하기 전이여서, 몸을 써야할 일이 많았고, 나는 시간 빌게이츠여서 한 두 번 가서 열심히 일을 하고 왔다. 일을 하면서 정이 좀 들었는지, 혹은, 친구의 창업은 내게 새로운 것을 하고 싶다라는 원동력을 들게 해주었다.

 그래도 한 평생 컴퓨터를 해왔는데, 역시 가상 세계의 해지호그를 만드는게 가장 쉽지 않을까 생각을 하게 되었고, 해지호그만의 홈페이지를 만들어 각종 사이트에 연결할 수 있는 브릿지 역할의 사이트를 만들어 보고 싶었다.

 

 근데, 나는 코틀린 밖에 못하는 바보인데, 사이트를 어떻게 만들 수 있을까?

 때마침 내 알고리즘에는 KMP 글이 꽤 많이 보이기 시작했고, Chat GPT 개발론으로 개발을 한 번 트라이 해본 얘기들을 해보려고 한다.

 

프로젝트 목표

- 메뉴 소개와 각종 플랫폼으로 이동할 수 있는 심플한 기능을 구현한다.

- 추후에 잘 되었을 때, 모바일 플랫폼을 고려해서 리소스와 UI, 비지니스 로직을 한 번만 작성한다.

 

 최종적으로 작성한 프로젝트 구조는 위와 같게 되었다.

 webApp이라는 별도의 모듈을 하나 더 만들었다. 플랫폼 별로 특수한 코드와 필요한 로직을 관리하기 위한 용도로 프로젝트 구조를 잡고 싶었다. android, js, wasmJs, desktop 네 가지의 플랫폼에 특화된 코드를 분리하여 작성하고 싶었다.

 그리고, 코드는 한 번만 작성하기 위해서 shared 모듈에서 UI와 비지니스 로직을 관리하여 프로젝트를 쓰고 싶었다. 이렇게만 프로젝트가 진행된다면, 코드는 모두 단벌만 작성될 것이고, 비지니스 로직이 공통되기 때문에 한 번의 작업은 4개의 OS 프로젝트에 배포를 할 수 있는 생산성이 무려 4배나 될 수 있을 것이라고 확신했다.

 

KMP를 조금 더 평범하게 쓰려면 할게 많아요..

  안드로이드 개발자는 안드로이드만 개발할 수 있고, 다른 OS에서 개발을 하려면 안드로이드에서 개발을 했던 만큼 공부를 다시 해야 그 경지까지 오르기 때문에 나와 같은 개발자들은 KMP를 정말 환영했을 지도 모른다. 그리고, 그런 기대감으로 프로젝트를 진행하게 되면, 그만큼 실망감도 크기 마련이다.

 KMP를 진행하다보면 우리는 알게 모르게, 특정 생태계에서 익수한 상태로 개발을 하고 있다는 것을 배울 수 있게 되었다.

 

1. 모듈 분리는 생각보다 어려웠다.

기존에 Android나 Desktop과 같이 익숙한 환경에서는 모듈을 쉽게 설정할 수 있었고, 많은 레퍼런스를 통해 그 방법을 참고할 수 있었다. 하지만 KMP에서는 초기 설정을 쉽게 제공하는 서비스가 있음에도 불구하고, 각 개발자의 요구에 맞추다 보면 결국 Gradle 설정을 반복해서 빌드해야 하는 삽질을 경험하게 된다. 특히, 나처럼 webApp이라는 별도의 모듈을 만들고 싶어하는 경우라면 이 과정이 더욱 복잡해진다.

예시 코드: Gradle에서 각 플랫폼별 모듈 설정

먼저, 프로젝트의 settings.gradle.kts에서 각 모듈을 선언하는 예시입니다:

// settings.gradle.kts
include(":shared")
include(":androidApp")
include(":webApp")
include(":desktopApp")
 

공통 모듈 shared 설정

공통 비즈니스 로직과 UI 코드를 관리하는 shared 모듈에서는 Kotlin Multiplatform을 설정합니다. 여기에 각 플랫폼별로 타겟을 정의해야 하는데, 이 부분에서 Gradle 설정을 자주 수정하게 됩니다.

// shared/build.gradle.kts
kotlin {
    jvm() // Desktop용 JVM 타겟
    android() // Android용 타겟
    js(IR) { // JavaScript 타겟, 웹용으로 사용
        browser()
        binaries.executable()
    }

    wasm32() // WebAssembly 타겟

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
                implementation("io.ktor:ktor-client-core:2.0.0") // 공통 네트워크 로직 예시
            }
        }
        val androidMain by getting
        val jsMain by getting
        val desktopMain by getting
        val wasmMain by getting
    }
}

플랫폼별 모듈 예시

각 플랫폼별로 특정 코드가 필요할 경우, 해당 모듈의 build.gradle.kts에서 별도의 설정을 추가해야 합니다. 예를 들어, webApp의 경우 아래와 같이 설정할 수 있습니다.

// webApp/build.gradle.kts
plugins {
    kotlin("multiplatform")
}

kotlin {
    js(IR) {
        browser {
            commonWebpackConfig {
                cssSupport.enabled = true // 웹에서 CSS 사용 설정
            }
        }
    }

    sourceSets {
        val webAppMain by getting {
            dependencies {
                implementation(project(":shared")) // 공통 모듈 참조
                implementation("io.ktor:ktor-client-js:2.0.0") // 웹 전용 네트워크 로직
            }
        }
    }
}
 

문제점: 빈번한 Gradle 빌드와 의존성 문제

여기서 겪을 수 있는 어려움은, 이렇게 각 플랫폼별로 모듈을 나누고 Gradle 파일을 설정할 때 의존성 충돌이나 빌드 시간이 길어지는 문제입니다. 예를 들어, webApp 모듈에서 특정 라이브러리가 WebAssembly(Wasm)를 지원하지 않으면 그 부분에서 에러가 발생할 수 있고, 이를 해결하기 위해서는 직접 대체 구현을 해야 하는 경우가 있습니다.

 
// 예시: wasm 모듈에서 지원되지 않는 경우
val wasmMain by getting {
    dependencies {
        // 특정 라이브러리가 Wasm을 지원하지 않으면 대체해야 함
        // 예: kotlinx.coroutines는 지원되지만, 다른 라이브러리들은 지원되지 않을 수 있음
    }
}

 


2. 우리가 쓰는 라이브러리는 특정 OS에서에 특화 되어있었어요..

 Dagger, Retrofit, Firebase 방금 머릿 속으로 생각한 안드로이드 삼대장 라이브러리는 안드로이드에서만 작동합니다. 순수 코틀린 라이브러리를 통해서 이를 모두 대체해야해요.
 Dagger는 Koin으로 대체할 수 있어요. Retrofit은 ktor로 대체할 수 있고, Firebase는 직접 클라이언트를 만들어서 작업을 해야해요. 안드로이드에서는 쉽게 사용할 수 있던 라이브러리가 되지 않는 경우가 많아서 라이브러리와 유사하게 동작하게 하기 위해서 삽질을 경험하게 되었습니다.

 

3. wasm은 아직도 베타에요..

  해지호그의 첫 삽은 compose wasm으로 시작을 했고, 프로젝트의 마무리는 compose html으로 마무리를 지었어요. 

wasm은 아직도 갈길이 멀었어요.

 - 사파리에서는 아직 compose wasm을 지원 해주지 않습니다. 

 - 한글 폰트를 입혀야 한글이 나와요

 - 한글이 입력되지 않아서 내가 직접 한글 입력 로직을 구현해야해요

 - wasm까지 지원해주지 않는 라이브러리가 대부분이에요.

 - wasm에서 대부분은 내가 다 직접 구현해야만 해요.

 

4. 대부분의 기능들은 쉽게 구현되지 않아요..

ex) 로컬의 사진 사용하기

 로컬에 있는 음식 사진을 보여주기 위해서 각 OS별로 이미지에 접근하기 위해서는 파일 경로를 통해서 파일을 로드하면 된다고 생각을 했었어요. 하지만, 그건 생각보다 쉽지 않았어요.

 빌드하는 환경에서 shared 모듈의 commonMain 폴더의 resources에서 각 OS의 플랫폼에 갖고 있는 리소스들을 복사해서 만들어주게 되고, OS에서 동일한 리소스를 바라볼 수 있는 환경을 제공할 수 있었습니다.

1. Android

Android에서 resources 폴더의 이미지를 androidApp 모듈의 res 폴더로 복사하는 Task를 추가할 수 있습니다.

// androidApp/build.gradle.kts
tasks.register<Copy>("copyResourcesToAndroid") {
    from("../shared/src/commonMain/resources")
    into("$buildDir/generated/res") // Android 리소스 경로로 복사
    include("**/*.png") // PNG 이미지 파일만 복사
}

tasks.named("preBuild").configure {
    dependsOn("copyResourcesToAndroid") // 빌드 전에 리소스 복사
}

2. Desktop (JVM)

Desktop용 애플리케이션에서는 리소스를 특정 디렉토리로 복사하여 실행 시 사용하도록 할 수 있습니다.

// desktopApp/build.gradle.kts
tasks.register<Copy>("copyResourcesToDesktop") {
    from("../shared/src/commonMain/resources")
    into("$buildDir/resources/main") // Desktop용 리소스 폴더로 복사
    include("**/*.png")
}

tasks.named("jar").configure {
    dependsOn("copyResourcesToDesktop") // jar 생성 전에 리소스 복사
}

 

3. JS (Web)

JS 모듈에서는 리소스를 public 디렉토리로 복사하여 웹에서 접근할 수 있도록 설정할 수 있습니다.

// webApp/build.gradle.kts
tasks.register<Copy>("copyResourcesToWeb") {
    from("../shared/src/commonMain/resources")
    into("$buildDir/distributions/resources") // Web용 리소스 경로
    include("**/*.png")
}

tasks.named("browserProductionWebpack").configure {
    dependsOn("copyResourcesToWeb") // 웹팩 빌드 전에 리소스 복사
}

단점들도 있지만, KMP는 그래도 매력적인 선택지인 것 같아요 

 

물론 KMP에는 몇 가지 단점이 있지만, 여전히 매력적인 선택지라고 생각합니다.

  • 초기 프로젝트 구성이 어렵지만, 그 허들만 넘으면 플랫폼별로 유사한 UI 코드 작성이 가능하다는 점이 큰 장점입니다. 안드로이드 개발자라면 익숙한 방식으로 다른 플랫폼의 UI도 구현할 수 있다는 사실은 학습 과정에서의 동기부여를 크게 높여주었습니다. 특히, 한 번 시스템을 구축해 놓으면 Android뿐만 아니라 다른 플랫폼에서도 비슷한 방식으로 UI를 작성할 수 있다는 점에서 장기적으로 시간을 절약할 수 있습니다.
  • 코드 재사용에 대한 고민을 깊게 하게 된다는 점도 흥미로웠습니다. 각기 다른 OS에서 동일한 UI 디자인을 적용하는 방법을 찾는 과정에서 자연스럽게 여러 플랫폼에서 어떻게 일관성을 유지할지 고민하게 되었습니다. 이런 점은 개발자로 하여금 더욱 구조적인 사고를 하게 만들며, 코드 품질 향상에도 도움이 됩니다.
  • 플랫폼 특화 기능보다는, 공통적으로 제공할 수 있는 서비스에 대해 고민하게 된다는 점도 KMP의 큰 장점입니다. 여러 플랫폼에서 동일한 기능을 제공하기 위해선 특정 플랫폼에만 국한된 기능보다는 모든 플랫폼에서 통일성 있게 사용할 수 있는 기능을 고민하게 됩니다. 이 과정에서 자연스럽게 서비스의 본질에 집중하게 되고, 더 나은 사용자 경험을 제공할 수 있는 기회를 얻게 됩니다.

 

'Android 공부 > KMM' 카테고리의 다른 글

KMP에서 Unresolved reference: IO 에러 땜질하는 법  (0) 2024.02.05
2024년의 NEW 감자튀김의 프로젝트  (0) 2024.01.31
KMM (2) sqldelight  (1) 2022.04.16
KMM (1) 시작  (1) 2022.04.16