Kotlin Multiplatform: 크로스플랫폼 모바일 개발 심층 가이드
Kotlin Multiplatform(KMP)을 활용한 크로스플랫폼 모바일 앱 개발의 핵심 개념, 아키텍처, 실전 팁을 다룹니다. iOS 및 Android에서 공유 로직을 효율적으로 재사용하는 방법을 알아보세요.
Kotlin Multiplatform: 크로스플랫폼 모바일 개발 심층 가이드
현대 모바일 앱 개발은 iOS와 Android라는 두 개의 주요 플랫폼을 동시에 지원해야 하는 복잡한 과제에 직면해 있습니다. 이로 인해 개발 비용과 시간이 증가하고, 플랫폼 간 기능 동기화에 어려움을 겪는 경우가 많습니다. 이러한 문제를 해결하기 위한 강력한 대안으로 Kotlin Multiplatform(KMP)이 주목받고 있으며, 특히 비즈니스 로직 공유를 통해 개발 효율성을 극대화하는 데 기여합니다. 이 글에서는 KMP의 핵심 개념부터 아키텍처, 실전 개발 팁 및 코드 예제까지 심층적으로 다루어, 여러분이 KMP를 활용한 크로스플랫폼 모바일 개발의 잠재력을 최대한 발휘할 수 있도록 돕겠습니다.
Kotlin Multiplatform(KMP)이란 무엇인가?
Kotlin Multiplatform은 JetBrains에서 개발한 오픈소스 기술로, 단일 코드베이스에서 작성된 Kotlin 코드를 다양한 플랫폼에서 재사용할 수 있도록 지원합니다. 특히 모바일 개발에서는 Android와 iOS 앱의 비즈니스 로직을 Kotlin으로 작성하고, 이를 두 플랫폼에서 공유하여 사용할 수 있게 해주는 것이 핵심입니다.
다른 크로스플랫폼 프레임워크인 React Native나 Flutter가 UI까지 포함한 전체 앱을 단일 코드베이스로 구축하는 데 초점을 맞추는 반면, KMP는 주로 비즈니스 로직, 데이터 모델, 네트워크 통신, 데이터베이스 연동 등 플랫폼 독립적인 코드의 공유에 강점을 가집니다. UI는 각 플랫폼의 네이티브 기술(Android의 Compose/XML, iOS의 SwiftUI/UIKit)을 사용하여 개발하므로, 사용자는 완전히 네이티브한 경험을 얻을 수 있습니다.
다음 표는 주요 크로스플랫폼 기술들의 특징을 비교한 것입니다.
| 특징 | Kotlin Multiplatform (KMP) | Flutter | React Native |
|---|---|---|---|
| 공유 범위 | 비즈니스 로직, 데이터 모델 | UI 및 비즈니스 로직 | UI 및 비즈니스 로직 |
| UI 구현 | 네이티브 UI (Compose/XML, SwiftUI/UIKit) | 자체 렌더링 엔진 (Skia) | 네이티브 컴포넌트 브릿징 |
| 성능 | 네이티브 수준 | 고성능 (컴파일된 C++ 코드) | 브릿지 오버헤드 발생 가능 |
| 언어 | Kotlin | Dart | JavaScript/TypeScript |
| 학습 곡선 | Kotlin 및 각 플랫폼 네이티브 기술 필요 | Dart 및 Flutter 프레임워크 | JavaScript/TypeScript 및 React |
| 생태계 | 성장 중 | 성숙 | 성숙 |
KMP는 네이티브 UI의 장점을 포기하지 않으면서도 핵심 로직의 중복 개발을 피하고 싶은 팀에 매우 매력적인 선택지입니다.
KMP의 핵심 장점
KMP를 모바일 개발에 도입함으로써 얻을 수 있는 여러 가지 장점들이 있습니다.
- 네이티브 성능과 UI/UX 유지: KMP는 비즈니스 로직만 공유하고 UI는 각 플랫폼의 네이티브 기술을 사용합니다. 이는 앱의 성능이 네이티브 수준으로 유지되고, iOS와 Android 사용자 모두에게 익숙하고 최적화된 UI/UX를 제공할 수 있다는 의미입니다. 특정 플랫폼의 최신 UI 트렌드나 기능에 빠르게 대응할 수 있습니다.
- Kotlin 언어의 이점: Kotlin은 간결하고 안전하며 생산적인 언어로, 이미 Android 개발의 주류 언어입니다. KMP를 통해 서버 개발(Ktor), 웹 프론트엔드(Kotlin/JS), 데스크톱(Compose Multiplatform) 등 다양한 플랫폼에서 Kotlin을 활용할 수 있어 개발자의 역량 확장에 유리합니다.
- 점진적 도입 가능성: 기존에 이미 개발된 iOS 및 Android 앱이 있는 경우에도 KMP를 점진적으로 도입할 수 있습니다. 전체 앱을 한 번에 전환할 필요 없이, 새로운 기능이나 특정 모듈부터 KMP로 개발하여 공유 로직을 적용할 수 있습니다. 이는 위험 부담을 줄이고 KMP의 장점을 단계적으로 활용할 수 있게 합니다.
- 코드 재사용성 극대화: 비즈니스 로직, 데이터 모델, 네트워크 통신, 데이터 파싱, 유틸리티 함수 등 플랫폼에 종속되지 않는 모든 코드를
commonMain모듈에서 작성하고 재사용할 수 있습니다. 이는 개발 시간 단축, 버그 감소, 기능 불일치 방지 등 여러 이점을 제공합니다.
KMP 아키텍처 이해
KMP 프로젝트는 기본적으로 여러 모듈로 구성됩니다. 각 모듈은 특정 플랫폼을 대상으로 하거나, 모든 플랫폼에서 공유되는 코드를 포함합니다.
-
commonMain: 모든 플랫폼에서 공유되는 코드를 작성하는 곳입니다. 플랫폼에 독립적인 비즈니스 로직, 데이터 모델, 인터페이스 등을 정의합니다. -
androidMain: Android 플랫폼에 특화된 코드를 작성하는 곳입니다.commonMain에서 정의된expect선언의actual구현이나, Android SDK에 직접 접근해야 하는 코드가 포함됩니다. -
iosMain: iOS 플랫폼에 특화된 코드를 작성하는 곳입니다.commonMain에서 정의된expect선언의actual구현이나, iOS 프레임워크에 직접 접근해야 하는 코드가 포함됩니다.
expect/actual 메커니즘
KMP의 핵심 기능 중 하나는 expect/actual 메커니즘입니다. 이는 commonMain에서 특정 기능의 선언(expect)만 하고, 각 플랫폼별 모듈(androidMain, iosMain)에서 해당 기능의 실제 구현(actual)을 제공하도록 강제하는 방법입니다.
예를 들어, 각 플랫폼의 OS 정보를 가져오는 기능을 공유하고 싶다면 commonMain에 다음과 같이 expect 선언을 할 수 있습니다.
// commonMain/kotlin/com/example/Platform.kt
package com.example
expect class Platform() {
val name: String
}
그리고 각 플랫폼별 모듈에서 이 expect 선언에 대한 actual 구현을 제공합니다.
// androidMain/kotlin/com/example/Platform.kt
package com.example
actual class Platform actual constructor() {
actual val name: String = "Android ${android.os.Build.VERSION.SDK_INT}"
}
// iosMain/kotlin/com/example/Platform.kt
package com.example
import platform.UIKit.UIDevice
actual class Platform actual constructor() {
actual val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}
이렇게 하면 commonMain에서 Platform().name을 호출하면, 빌드 시점에 각 타겟 플랫폼에 맞는 actual 구현이 연결되어 실행됩니다.
실전 KMP 개발 환경 설정
KMP 프로젝트를 시작하려면 Gradle을 기반으로 한 설정이 필요합니다.
Gradle 설정
build.gradle.kts 파일은 KMP 프로젝트의 핵심 설정 파일입니다. commonMain 모듈과 플랫폼별 모듈의 의존성을 관리합니다.
// shared/build.gradle.kts (Kotlin DSL)
plugins {
kotlin("multiplatform")
kotlin("native.cocoapods") // iOS 통합을 위한 플러그인
kotlin("plugin.serialization") version "1.9.22" // kotlinx.serialization 사용 시
}
kotlin {
androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
iosX64() // iOS 시뮬레이터 64비트
iosArm64() // iOS 기기 Arm64
iosSimulatorArm64() // M1 맥에서 iOS 시뮬레이터
// iOS 통합 설정
cocoapods {
summary = "Some description for the Shared Module"
homepage = "Link to the Shared Module homepage"
version = "1.0"
ios.deploymentTarget = "14.1"
framework {
baseName = "shared" // Swift/Objective-C에서 임포트할 모듈 이름
}
}
sourceSets {
val commonMain by getting {
dependencies {
// KMP에서 사용 가능한 공통 라이브러리
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
implementation("io.ktor:ktor-client-core:2.3.6")
implementation("io.ktor:ktor-client-content-negotiation:2.3.6")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
}
}
val androidMain by getting {
dependencies {
// Android 특화 라이브러리 (예: Ktor Android 엔진)
implementation("io.ktor:ktor-client-android:2.3.6")
}
}
val iosMain by getting {
dependencies {
// iOS 특화 라이브러리 (예: Ktor Darwin 엔진)
implementation("io.ktor:ktor-client-darwin:2.3.6")
}
}
}
}
android {
namespace = "com.example.shared"
compileSdk = 34
defaultConfig {
minSdk = 24
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
Xcode 통합 (iOS)
KMP 모듈을 iOS 프로젝트에서 사용하려면, 빌드된 프레임워크를 Xcode 프로젝트에 임포트해야 합니다. kotlin("native.cocoapods") 플러그인을 사용하면 이 과정이 매우 편리해집니다.
- Podfile 설정: iOS 프로젝트의
Podfile에shared모듈을 추가합니다.
# Podfile
target 'YourApp' do
use_frameworks!
platform :ios, '14.1'
# ... 다른 Pods ...
# KMP shared 모듈 임포트 (경로를 맞게 설정)
pod 'shared', :path => '../shared' # shared 모듈이 iOS 프로젝트와 같은 레벨에 있을 경우
end
- Pod Install: 터미널에서
pod install을 실행하여 KMP 프레임워크를 Xcode 프로젝트에 통합합니다.
- Xcode에서 사용: 이제 Swift 코드에서
import shared를 통해 KMP 모듈의 클래스와 함수에 접근할 수 있습니다.
// iOS App의 Swift 파일
import SwiftUI
import shared // KMP 모듈 임포트
struct ContentView: View {
let platform = Platform() // commonMain의 Platform 클래스 사용
var body: some View {
VStack {
Text("Hello, \(platform.name)")
}
}
}
공유 로직 작성 예제
KMP의 가장 큰 장점은 비즈니스 로직을 공유하는 것입니다. 다음은 몇 가지 일반적인 공유 로직 예시입니다.
1. 데이터 모델 공유
API 응답이나 앱 내부에서 사용되는 데이터 클래스는 commonMain에 정의하여 두 플랫폼에서 모두 활용할 수 있습니다.
// commonMain/kotlin/com/example/model/User.kt
package com.example.model
import kotlinx.serialization.Serializable
@Serializable // kotlinx.serialization을 사용하여 JSON 직렬화/역직렬화 가능
data class User(
val id: String,
val name: String,
val email: String
)
2. 네트워크 통신 로직 공유 (Ktor)
Ktor 클라이언트는 KMP를 완벽하게 지원하여, 네트워크 통신 로직을 commonMain에서 작성하고 각 플랫폼별 엔진을 사용하여 실행할 수 있습니다.
// commonMain/kotlin/com/example/network/ApiClient.kt
package com.example.network
import com.example.model.User
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
// Platform-agnostic HttpClient 생성 함수 (expect/actual 사용 가능)
expect fun getHttpClient(): HttpClient
class ApiClient {
private val httpClient = getHttpClient()
suspend fun getUsers(): List<User> {
return httpClient.get("https://api.example.com/users").body()
}
// 다른 API 호출 메서드 추가
}
expect fun getHttpClient(): HttpClient에 대한 actual 구현은 각 플랫폼별 모듈에서 제공합니다.
// androidMain/kotlin/com/example/network/HttpClient.kt
package com.example.network
import io.ktor.client.HttpClient
import io.ktor.client.engine.android.Android
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
actual fun getHttpClient(): HttpClient {
return HttpClient(Android) {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
isLenient = true
})
}
}
}
// iosMain/kotlin/com/example/network/HttpClient.kt
package com.example.network
import io.ktor.client.HttpClient
import io.ktor.client.engine.darwin.Darwin
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
actual fun getHttpClient(): HttpClient {
return HttpClient(Darwin) {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
isLenient = true
})
}
}
}
이제 commonMain의 ApiClient는 getHttpClient()를 통해 플랫폼에 맞는 HTTP 클라이언트를 사용하여 네트워크 요청을 수행할 수 있습니다.
KMP와 UI 프레임워크
KMP는 기본적으로 UI를 공유하지 않고 각 플랫폼의 네이티브 UI 기술을 활용합니다.
- Android: Jetpack Compose 또는 XML 레이아웃을 사용하여 UI를 구축합니다. KMP 공유 모듈에서 데이터를 가져와 UI에 바인딩합니다.
- iOS: SwiftUI 또는 UIKit을 사용하여 UI를 구축합니다. KMP 공유 모듈을 Swift/Objective-C 프레임워크로 임포트하여 데이터를 가져와 UI에 표시합니다.
최근에는 Jetpack Compose의 Multiplatform 버전인 Compose Multiplatform이 등장하여, Android뿐만 아니라 iOS, Desktop, Web 등에서도 Kotlin으로 UI를 구축할 수 있는 가능성을 열었습니다. 하지만 iOS에서의 Compose Multiplatform은 아직 실험적인 단계에 있으며, 성능 및 생태계 측면에서 더 많은 발전이 필요합니다. 따라서 현재 모바일 개발의 주류 KMP 워크플로우에서는 비즈니스 로직 공유에 집중하고 UI는 네이티브 기술을 사용하는 것이 일반적입니다.
KMP 도입 시 고려사항 및 팁
KMP는 강력한 도구이지만, 도입 전에 몇 가지 고려사항을 염두에 두어야 합니다.
- 학습 곡선: 개발 팀이 Kotlin 언어에 익숙해야 하며, iOS 개발자라면 Kotlin 언어와 KMP의
expect/actual메커니즘에 대한 학습이 필요합니다. 또한, 각 플랫폼의 네이티브 UI 개발 지식은 여전히 필수적입니다. - 라이브러리 생태계:
commonMain에서 사용할 수 있는 라이브러리는 Multiplatform을 지원해야 합니다.kotlinx.coroutines,kotlinx.serialization,Ktor와 같은 JetBrains 공식 라이브러리는 KMP를 완벽하게 지원하지만, 특정 플랫폼에만 존재하는 라이브러리(예: iOS의 CoreLocation)는expect/actual을 통해 래핑해야 합니다. - 빌드 및 디버깅: KMP 프로젝트의 빌드 설정은 기존 단일 플랫폼 프로젝트보다 복잡할 수 있습니다. 특히 iOS 부분의 Gradle-Xcode 통합 과정에서 예상치 못한 문제가 발생할 수 있습니다. 디버깅도 Android Studio와 Xcode를 오가며 진행해야 할 수 있습니다.
- 점진적 도입 전략: 처음부터 모든 것을 KMP로 전환하기보다는, 신규 기능이나 독립적인 모듈부터 KMP를 적용하여 팀의 숙련도를 높이고 안정성을 확보하는 것이 좋습니다. 예를 들어, 인증 모듈, 결제 로직, 분석 로직 등 플랫폼에 크게 의존하지 않는 부분부터 시작할 수 있습니다.
- 팀 구성: KMP는 Android 개발자와 iOS 개발자 간의 협업을 더욱 긴밀하게 만듭니다. 공유 모듈에 대한 공동의 이해와 책임이 중요하며, 크로스펑셔널 팀 구성이 효과적일 수 있습니다.
마무리
Kotlin Multiplatform은 모바일 앱 개발에서 비즈니스 로직 공유를 통해 개발 효율성을 혁신적으로 높일 수 있는 강력한 기술입니다. 네이티브 UI/UX의 장점을 유지하면서 코드 재사용을 극대화하고, Kotlin이라는 현대적인 언어의 이점을 활용할 수 있다는 점에서 많은 개발 팀에게 매력적인 선택지가 되고 있습니다. KMP의 아키텍처와 실전 팁을 잘 이해하고 점진적인 도입 전략을 통해 여러분의 모바일 개발 프로젝트에 성공적으로 적용하시기를 바랍니다.
관련 게시글
App Store Optimization ASO Strategy: React Native & Flutter 앱 성공 비결
모바일 앱 시장에서 성공하기 위한 App Store Optimization (ASO) 전략을 React Native, Flutter와 같은 크로스플랫폼 앱 개발 관점에서 상세히 알아봅니다. 키워드, 시각적 요소, 평점 관리 등 실전 팁을 제공합니다.
React Native Performance Optimization: 실전 가이드
React Native 앱의 성능을 최적화하여 iOS 및 Android 사용자 경험을 향상시키는 실용적인 팁과 코드 예제를 제공합니다. JavaScript 스레드, UI 렌더링, 번들 사이즈 등 다양한 측면에서 최적화 전략을 다룹니다.
Flutter State Management: Riverpod vs Bloc 완전 비교 가이드
Flutter 앱 개발에서 가장 인기 있는 상태관리 솔루션인 Riverpod과 Bloc을 실전 코드 예제와 함께 상세히 비교 분석합니다.