Groo

Kotlin Multiplatform 앱 첫 빌드하기 본문

Kotlin Multiplatform

Kotlin Multiplatform 앱 첫 빌드하기

김주엽 2024. 1. 14. 20:26

commonMain 모듈에는 각 플랫폼 모듈에서 공통으로 사용할 수 있는 코드가 포함되어 있어요. 하지만 해당 모듈은 각 플랫폼과 독립적으로 운영되도록 디자인 되어서 만약 여러분이 특정 플랫폼의 api를 해당 모듈에서 작성한다면 ide는 에러를 발생 시킬거에요.

 

KMP 프로젝트를 생성하면 commonMain 모듈에 기본으로 Greeting.kt 파일이 추가되어 있을거에요. 내부 greet 함수에서 Java의 Random 클래스를 사용해보세요. 그러면 ide에서 해당 Random 클래스는 사용할 수 없다고 여러분께 말할거에요. commonMain 모듈에서는 순수 Kotlin으로만 코드를 작성할 수 있기 때문이죠.

import java.util.Random

fun greet(): String {
    val firstWord = if (Random().nextBoolean()) "Hi!" else "Hello!"
}

만약 해당 모듈에서 Random 클래스를 사용하고 싶다면 Java의 Random 대신 Kotlin의 Random을 사용하면 돼요.

import kotlin.random.Random

class Greeting {
    private val platform: Platform = getPlatform()

    fun greet(): String {
        val firstWord = if (Random.nextBoolean()) "Hi!" else "Hello!"

        return "$firstWord\nGuess what it is! > ${platform.name.reversed()}!"
    }
}

이처럼 각 플랫폼별 api를 사용하지 않고 순수 Kotlin으로만 코드를 작성하는 것은 분명히 한계가 있을거에요. 하지만 이 문제는 interface나 expect/actual 매커니즘을 활용해서 해결할 수 있어요.

 

commonMain 모듈에서는 expected 제어자를 활용해서 변수나 메서드를 선언하고 각 플랫폼 모듈에서는 actual 제어자를 활용해서 앞서 expected로 선언된 변수나 메서드에 구현체를 제공해요. 특정 플랫폼이 빌드될 때 Kotlin 컴파일러는 expected와 actual로 선언된 코드를 서로 병합하여 하나의 코드로 생성해요.

 

commonMain 모듈에 기본으로 추가되어 있는 Platform.kt 파일을 가지고 조금 더 이야기 해볼게요. Platform 인터페이스는 각 플랫폼에 대한 정보를 나타내는 인터페이스에요. 각 플랫폼 모듈에서는 Platform 인터페이스를 구현한 것을 볼 수 있어요.

interface Platform {
    val name: String
}

안드로이드는 인터페이스를 구현할 때 플랫폼 api인 Build 클래스를 사용해서 name 변수를 초기화 하고 있어요. androidMain 모듈은 Kotlin/JVM을 기반으로 빌드되기 때문에 Java의 Random 클래스에 접근하더라도 문제가 없어요.

// Platform.kt in androidMain module:
import android.os.Build

class AndroidPlatform: Platform {
    override val name: String =
        "Android ${Build.VERSION.SDK_INT}"
}

iOS는 인터페이스를 구현할 때 플랫폼 api인 UIDevice 클래스를 사용해서 name 변수를 초기화 하고 있어요. iosMain 모듈은 Kotlin/Native를 기반으로 빌드되기 때문에 플랫폼 api 클래스이더라도 Kotlin으로 코드를 작성할 수 있어요.

// Platform.kt in the iosMain module:
import platform.UIKit.UIDevice

class IOSPlatform: Platform {
    override val name: String =
        UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}

추가로 각 모듈에 존재하는 getPlatform 메서드를 확인해보세요. commonMain 모듈에서는 expected 제어자와 함께 메서드가 선언되었고 body는 없는 모습이지만 각 플랫폼 모듈에서는 actual 제어자와 함께 메서드가 선언되었고 구현체도 존재하는 것을 볼 수 있어요.

// Platform.kt in commonMain module:
expect fun getPlatform(): Platform
// Platform.kt in androidMain module:
actual fun getPlatform(): Platform = AndroidPlatform()
// Platform.kt in iosMain module:
actual fun getPlatform(): Platform = IOSPlatform()

그럼 이제 앱을 실행해볼까요?


글 작성시 참고한 문서

샘플 코드

Comments