Proyek Kotlin biasa memiliki satu source set main yang dikompilasi ke satu target (biasanya JVM). Proyek KMP berbeda: satu proyek mendeklarasikan beberapa target platform, dan kompilator Kotlin mengelola source set per target plus source set bersama commonMain. Memahami hubungan antar source set—mana yang berbagi kode, mana yang spesifik platform—adalah pondasi yang harus dikuasai sebelum menulis kode KMP nyata. Pada chapter ini kita akan membedah deklarasi target di build.gradle.kts, hierarki source set, dan bagaimana satu modul commonMain dapat dipakai oleh lima target sekaligus.
Deklarasi Target di build.gradle.kts #
Blok kotlin { ... } dalam build.gradle.kts mendeklarasikan target platform yang didukung proyek. Setiap target menghasilkan artifact berbeda—androidTarget() menghasilkan AAR/APK, iosX64() dan iosArm64() menghasilkan framework iOS, jvm() menghasilkan JAR. Pemilihan target menentukan source set yang dibuat secara otomatis.
plugins {
kotlin("multiplatform") version "2.0.21"
}
kotlin {
androidTarget()
iosX64()
iosArm64()
iosSimulatorArm64()
jvm()
}Empat target iOS umumnya ditambahkan bersamaan—tiga untuk arsitektur yang berbeda (x64 simulator Intel, ARM64 device, ARM64 simulator Apple Silicon). Kompilator Kotlin akan menghasilkan framework per arsitektur, kemudian Xcode melakukan fat framework atau XCFramework untuk menggabungkannya.
Hierarki Source Set #
Source set adalah direktori yang berisi kode Kotlin. KMP membangun hierarki source set agar kode dapat berbagi pada level yang sesuai. commonMain adalah root—kode di sini dipakai semua target. iosMain adalah intermediate—dipakai semua target iOS tetapi tidak oleh Android atau JVM. iosX64Main spesifik untuk iOS x64 saja.
// multiplatform_project_structure.kt
// build.gradle.kts with kotlin { androidTarget(); iosX64(); iosArm64() }.
// Source sets: commonMain, jvmMain, nativeMain.
// This demo simulates the source-set hierarchy as data structures.
data class SourceSet(
val name: String,
val dependsOn: List<String>,
val description: String
)
data class Target(
val preset: String, // androidTarget, iosX64, iosArm64, iosSimulatorArm64, jvm
val platform: String,
val outputType: String // APK, framework, JAR
)
data class ProjectLayout(
val targets: List<Target>,
val sourceSets: List<SourceSet>
)
// --- KMP project layout for Android + iOS + JVM ---
fun sampleKmpLayout(): ProjectLayout {
val targets = listOf(
Target("androidTarget()", "Android", "AAR / APK"),
Target("iosX64()", "iOS x64 (simulator Intel)", "framework"),
Target("iosArm64()", "iOS ARM64 (device)", "framework"),
Target("iosSimulatorArm64()", "iOS ARM64 (simulator M1/M2)", "framework"),
Target("jvm()", "JVM", "JAR")
)
val sourceSets = listOf(
SourceSet("commonMain", emptyList(),
"Kode yang dipakai di semua target; pure Kotlin"),
SourceSet("androidMain", listOf("commonMain"),
"Implementasi Android: android.util.Log, Context, SharedPreferences"),
SourceSet("iosMain", listOf("commonMain"),
"Kode intermediate yang dipakai semua target iOS"),
SourceSet("iosX64Main", listOf("iosMain"),
"Spesifik iOS x64 (simulator Intel)"),
SourceSet("iosArm64Main", listOf("iosMain"),
"Spesifik iOS ARM64 (device)"),
SourceSet("iosSimulatorArm64Main", listOf("iosMain"),
"Spesifik iOS ARM64 (simulator M1/M2)"),
SourceSet("jvmMain", listOf("commonMain"),
"Implementasi JVM: java.io.File, java.net.URL, JDBC")
)
return ProjectLayout(targets, sourceSets)
}
// --- Print build.gradle.kts equivalent (illustrative) ---
fun buildGradleKts(): String = """
plugins {
kotlin("multiplatform") version "2.0.21"
}
kotlin {
androidTarget()
iosX64()
iosArm64()
iosSimulatorArm64()
jvm()
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
}
}
val androidMain by getting
val iosMain by creating {
dependsOn(commonMain)
}
val iosX64Main by getting { dependsOn(iosMain) }
val iosArm64Main by getting { dependsOn(iosMain) }
val iosSimulatorArm64Main by getting { dependsOn(iosMain) }
val jvmMain by getting
}
}
""".trim()
fun main() {
val layout = sampleKmpLayout()
println("=== Target Platform ===")
for (t in layout.targets) {
println(" ${t.preset.padEnd(28)} -> ${t.platform.padEnd(28)} output: ${t.outputType}")
}
println()
println("=== Source Sets ===")
for (ss in layout.sourceSets) {
val deps = if (ss.dependsOn.isEmpty()) "(top-level)" else ss.dependsOn.joinToString(", ")
println(" ${ss.name.padEnd(28)} depends-on: $deps")
println(" ${ss.description}")
}
println()
println("=== build.gradle.kts (ringkas) ===")
println(buildGradleKts())
}Output yang Dihasilkan:
=== Target Platform ===
androidTarget() -> Android output: AAR / APK
iosX64() -> iOS x64 (simulator Intel) output: framework
iosArm64() -> iOS ARM64 (device) output: framework
iosSimulatorArm64() -> iOS ARM64 (simulator M1/M2) output: framework
jvm() -> JVM output: JAR
=== Source Sets ===
commonMain depends-on: (top-level)
Kode yang dipakai di semua target; pure Kotlin
androidMain depends-on: commonMain
Implementasi Android: android.util.Log, Context, SharedPreferences
iosMain depends-on: commonMain
Kode intermediate yang dipakai semua target iOS
iosX64Main depends-on: iosMain
Spesifik iOS x64 (simulator Intel)
iosArm64Main depends-on: iosMain
Spesifik iOS ARM64 (device)
iosSimulatorArm64Main depends-on: iosMain
Spesifik iOS ARM64 (simulator M1/M2)
jvmMain depends-on: commonMain
Implementasi JVM: java.io.File, java.net.URL, JDBC
=== build.gradle.kts (ringkas) ===
plugins {
kotlin("multiplatform") version "2.0.21"
}
kotlin {
androidTarget()
iosX64()
iosArm64()
iosSimulatorArm64()
jvm()
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
}
}
val androidMain by getting
val iosMain by creating {
dependsOn(commonMain)
}
val iosX64Main by getting { dependsOn(iosMain) }
val iosArm64Main by getting { dependsOn(iosMain) }
val iosSimulatorArm64Main by getting { dependsOn(iosMain) }
val jvmMain by getting
}
}Mari kita bedah bagian kunci dari kode di atas:
SourceSet(name, dependsOn, description): model data yang menggambarkan satu source set.dependsOnadalah daftar source set yang menjadi parent—androidMainbergantung padacommonMain, sehingga kode dicommonMaindapat dipanggil dariandroidMain.Target(preset, platform, outputType): model data untuk satu target platform.presetadalah nama fungsi yang dipanggil di blokkotlin {}(mis.androidTarget());outputTypeadalah jenis artifact yang dihasilkan.sampleKmpLayout: membangun layout lengkap untuk proyek KMP dengan 5 target.iosMaindiciptakan sebagai intermediate source set—kode di sini dipakai olehiosX64Main,iosArm64Main, daniosSimulatorArm64Main.buildGradleKts: string template yang merepresentasikanbuild.gradle.ktsringkas. BloksourceSetsmendeklarasikan hubungandependsOnsecara eksplisit—iosMainbergantung padacommonMain, dan ketiga source set iOS spesifik bergantung padaiosMain.- Output menunjukkan hierarki lengkap:
commonMainadalah top-level,androidMain/iosMain/jvmMainadalah turunan langsung, daniosX64Main/iosArm64Main/iosSimulatorArm64Mainadalah turunan dariiosMain. Kode dicommonMaindapat dipanggil dari semua source set; kode diiosMainhanya dari source set iOS.
Struktur Direktori #
Pada filesystem, source set direpresentasikan sebagai direktori src/<source-set>/kotlin/. Struktur tipikal proyek KMP:
my-app/
├── build.gradle.kts
├── settings.gradle.kts
└── src/
├── commonMain/
│ └── kotlin/
│ └── com/example/shared/
│ └── LogikaBisnis.kt
├── androidMain/
│ └── kotlin/
│ └── com/example/shared/
│ └── Platform.android.kt
├── iosMain/
│ └── kotlin/
│ └── com/example/shared/
│ └── Platform.ios.kt
└── jvmMain/
└── kotlin/
└── com/example/shared/
└── Platform.jvm.ktKonvensi nama file dengan sufiks .android.kt, .ios.kt, .jvm.kt memudahkan navigasi—semua file untuk class yang sama (Platform) berdampingan di editor. Paket (com.example.shared) harus sama di semua source set agar kompilator dapat mencocokkan deklarasi expect dengan actual.
Source Set Intermediate #
iosMain adalah contoh intermediate source set—tidak ada target langsung yang menggunakannya, namun ketiga source set iOS spesifik (iosX64Main, iosArm64Main, iosSimulatorArm64Main) mewarisinya. Intermediate source set berguna bila kode iOS spesifik (mis. pemanggilan UIKit) ingin dibagi antara ketiga target iOS tanpa menulis ulang.
kotlin {
iosX64()
iosArm64()
iosSimulatorArm64()
sourceSets {
val iosMain by creating {
dependsOn(commonMain)
}
val iosX64Main by getting { dependsOn(iosMain) }
val iosArm64Main by getting { dependsOn(iosMain) }
val iosSimulatorArm64Main by getting { dependsOn(iosMain) }
}
}Pada Kotlin 2.0+, hierarki source set untuk iOS dibuat otomatis oleh plugin—iosMain tersedia tanpa deklarasi manual. Versi sebelumnya memerlukan deklarasi creating seperti di atas. Tetap periksa dokumentasi terbaru karena konvensi berubah antar versi.
Dependency per Source Set #
Setiap source set dapat memiliki dependency sendiri. commonMain memakai library yang mendukung KMP (mis. kotlinx-coroutines-core); androidMain dapat menambah library Android-specific (mis. androidx.lifecycle); jvmMain dapat menambah library JVM-only (mis. postgresql).
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}
}
val androidMain by getting {
dependencies {
implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.2")
}
}
val jvmMain by getting {
dependencies {
implementation("org.postgresql:postgresql:42.6.0")
}
}
}Library di commonMain otomatis tersedia di semua source set turunan. Library di androidMain hanya tersedia di androidMain dan source set turunannya—tidak bocor ke jvmMain.
Referensi Lanjutan #
- Discover KMP project — panduan struktur proyek KMP termasuk target dan source set
- Hierarchy template — dokumentasi hierarchy template untuk pengelompokan source set antar target
Penutup #
Struktur proyek KMP—deklarasi target, hierarki source set, dan dependency per source set—adalah konsep yang terasa asing bagi pengembang yang terbiasa dengan proyek single-platform. Setelah pola ini dipahami, KMP memberikan keuntungan besar: kode bisnis ditulis sekali, dan setiap platform mendapatkan artifact yang sesuai. Chapter berikutnya akan masuk lebih dalam ke commonMain dan platform source sets—bagaimana kode diorganisir di antara keduanya.