Setelah memahami struktur source set dan pola kode platform-specific, kita perlu mengelola dependency eksternal. Library yang dipakai di commonMain harus kompatibel dengan semua target—tidak boleh hanya berjalan di JVM. Library platform-specific ditambahkan ke source set spesifik. Gradle mengelola transitivitas dependency: library A yang membutuhkan library B akan otomatis menarik B ke classpath. Pada chapter ini kita akan membedah deklarasi dependency di build.gradle.kts, melihat resolusi transitif, dan memahami perbedaan classpath antara commonMain, androidMain, iosMain, dan jvmMain.
Dependency commonMain: Hanya Library KMP #
Library yang ditambahkan ke commonMain harus mendukung multiplatform. Library seperti kotlinx-coroutines-core, kotlinx-serialization-json, kotlinx-datetime, dan ktor-client-core menyediakan artifact KMP yang berisi kode commonMain plus source set spesifik per target. Gradle akan otomatis memilih artifact yang sesuai untuk setiap target.
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0")
implementation("io.ktor:ktor-client-core:2.3.7")
implementation("io.ktor:ktor-client-content-negotiation:2.3.7")
}
}
}
}Library di commonMain otomatis tersedia di semua source set turunan—androidMain, iosMain, jvmMain tidak perlu mendeklarasikan ulang.
Dependency Platform-Specific #
Library yang belum mendukung KMP—misalnya okhttp, postgresql, atau lifecycle-viewmodel—harus ditambahkan ke source set spesifik. Gradle memastikan library jvmMain tidak bocor ke iosMain, sehingga kompilasi iOS tidak error karena mencoba memuat class JVM-only.
sourceSets {
val androidMain by getting {
dependencies {
implementation("io.ktor:ktor-client-okhttp:2.3.7")
implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.2")
}
}
val iosMain by getting {
dependencies {
implementation("io.ktor:ktor-client-darwin:2.3.7")
}
}
val jvmMain by getting {
dependencies {
implementation("io.ktor:ktor-client-okhttp:2.3.7")
implementation("org.postgresql:postgresql:42.6.0")
}
}
}Simulasi Resolusi Dependency #
Demo berikut mensimulasikan bagaimana Gradle me-resolve dependency transitif per source set. Library ktor-client-content-negotiation bergantung pada ktor-client-core, yang otomatis ditarik ke classpath. Library ktor-client-okhttp bergantung pada ktor-client-core dan okhttp. Kode commonMain hanya melihat ktor-client-core; kode jvmMain dan androidMain melihat keduanya; kode iosMain hanya melihat ktor-client-darwin (plus ktor-client-core dari commonMain).
// multiplatform_dependencies.kt
// Manajemen dependency KMP: commonMain (KMP-compatible) vs platform-specific.
// Demo: simulasi resolusi dependency tree per source set.
data class Artifact(
val group: String,
val name: String,
val version: String,
val targets: Set<String>, // target yang kompatibel, atau "common"
val transitive: List<String> = emptyList()
) {
fun notation(): String = "$group:$name:$version"
}
data class SourceSetDependencies(
val sourceSet: String,
val direct: List<String>, // artifact notations
val inherited: List<String> = emptyList()
) {
fun all(): List<String> = (inherited + direct).distinct().sorted()
}
class DependencyResolver(private val catalog: Map<String, Artifact>) {
fun resolve(sourceSet: String, deps: List<String>): Set<String> {
val result = mutableSetOf<String>()
val queue = ArrayDeque(deps)
while (queue.isNotEmpty()) {
val notation = queue.removeFirst()
if (notation in result) continue
result.add(notation)
val art = catalog[notation] ?: continue
for (t in art.transitive) queue.addLast(t)
}
return result
}
}
fun main() {
val catalog = mutableMapOf<String, Artifact>()
fun put(a: Artifact) { catalog[a.notation()] = a }
put(Artifact("org.jetbrains.kotlinx", "kotlinx-coroutines-core", "1.7.3",
setOf("common"),
listOf("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")))
put(Artifact("org.jetbrains.kotlinx", "kotlinx-serialization-json", "1.6.3",
setOf("common"),
listOf("org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.3")))
put(Artifact("org.jetbrains.kotlinx", "kotlinx-serialization-core", "1.6.3",
setOf("common")))
put(Artifact("org.jetbrains.kotlinx", "kotlinx-datetime", "0.6.0",
setOf("common")))
put(Artifact("io.ktor", "ktor-client-core", "2.3.7",
setOf("common")))
put(Artifact("io.ktor", "ktor-client-content-negotiation", "2.3.7",
setOf("common"),
listOf("io.ktor:ktor-client-core:2.3.7")))
put(Artifact("com.squareup.okhttp3", "okhttp", "4.12.0",
setOf("jvm", "android")))
put(Artifact("io.ktor", "ktor-client-okhttp", "2.3.7",
setOf("jvm", "android"),
listOf("io.ktor:ktor-client-core:2.3.7", "com.squareup.okhttp3:okhttp:4.12.0")))
put(Artifact("io.ktor", "ktor-client-darwin", "2.3.7",
setOf("ios")))
put(Artifact("androidx.lifecycle", "lifecycle-viewmodel", "2.6.2",
setOf("android")))
put(Artifact("org.postgresql", "postgresql", "42.6.0",
setOf("jvm")))
val resolver = DependencyResolver(catalog)
val commonDeps = listOf(
"org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3",
"org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3",
"org.jetbrains.kotlinx:kotlinx-datetime:0.6.0",
"io.ktor:ktor-client-content-negotiation:2.3.7"
)
val androidDeps = listOf(
"io.ktor:ktor-client-okhttp:2.3.7",
"androidx.lifecycle:lifecycle-viewmodel:2.6.2"
)
val iosDeps = listOf(
"io.ktor:ktor-client-darwin:2.3.7"
)
val jvmDeps = listOf(
"io.ktor:ktor-client-okhttp:2.3.7",
"org.postgresql:postgresql:42.6.0"
)
val commonResolved = resolver.resolve("commonMain", commonDeps)
val androidResolved = resolver.resolve("androidMain", androidDeps + commonDeps)
val iosResolved = resolver.resolve("iosMain", iosDeps + commonDeps)
val jvmResolved = resolver.resolve("jvmMain", jvmDeps + commonDeps)
println("=== commonMain dependencies (final classpath) ===")
commonResolved.sorted().forEach { println(" - $it") }
println()
println("=== androidMain dependencies (final classpath) ===")
androidResolved.sorted().forEach { println(" - $it") }
println()
println("=== iosMain dependencies (final classpath) ===")
iosResolved.sorted().forEach { println(" - $it") }
println()
println("=== jvmMain dependencies (final classpath) ===")
jvmResolved.sorted().forEach { println(" - $it") }
println()
println("=== Ringkasan perbandingan ===")
println("commonMain : ${commonResolved.size} artifacts")
println("androidMain: ${androidResolved.size} artifacts (common + platform)")
println("iosMain : ${iosResolved.size} artifacts (common + platform)")
println("jvmMain : ${jvmResolved.size} artifacts (common + platform)")
println()
println("=== Source set spesifik (platform-only) ===")
val androidOnly = androidResolved - commonResolved
val iosOnly = iosResolved - commonResolved
val jvmOnly = jvmResolved - commonResolved
println("androidMain khusus: ${androidOnly.sorted()}")
println("iosMain khusus : ${iosOnly.sorted()}")
println("jvmMain khusus : ${jvmOnly.sorted()}")
}Output yang Dihasilkan:
=== commonMain dependencies (final classpath) ===
- io.ktor:ktor-client-content-negotiation:2.3.7
- io.ktor:ktor-client-core:2.3.7
- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3
- org.jetbrains.kotlinx:kotlinx-datetime:0.6.0
- org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.3
- org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3
=== androidMain dependencies (final classpath) ===
- androidx.lifecycle:lifecycle-viewmodel:2.6.2
- com.squareup.okhttp3:okhttp:4.12.0
- io.ktor:ktor-client-content-negotiation:2.3.7
- io.ktor:ktor-client-core:2.3.7
- io.ktor:ktor-client-okhttp:2.3.7
- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3
- org.jetbrains.kotlinx:kotlinx-datetime:0.6.0
- org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.3
- org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3
=== iosMain dependencies (final classpath) ===
- io.ktor:ktor-client-content-negotiation:2.3.7
- io.ktor:ktor-client-core:2.3.7
- io.ktor:ktor-client-darwin:2.3.7
- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3
- org.jetbrains.kotlinx:kotlinx-datetime:0.6.0
- org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.3
- org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3
=== jvmMain dependencies (final classpath) ===
- com.squareup.okhttp3:okhttp:4.12.0
- io.ktor:ktor-client-content-negotiation:2.3.7
- io.ktor:ktor-client-core:2.3.7
- io.ktor:ktor-client-okhttp:2.3.7
- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3
- org.jetbrains.kotlinx:kotlinx-datetime:0.6.0
- org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.3
- org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3
- org.postgresql:postgresql:42.6.0
=== Ringkasan perbandingan ===
commonMain : 6 artifacts
androidMain: 9 artifacts (common + platform)
iosMain : 7 artifacts (common + platform)
jvmMain : 9 artifacts (common + platform)
=== Source set spesifik (platform-only) ===
androidMain khusus: [androidx.lifecycle:lifecycle-viewmodel:2.6.2, com.squareup.okhttp3:okhttp:4.12.0, io.ktor:ktor-client-okhttp:2.3.7]
iosMain khusus : [io.ktor:ktor-client-darwin:2.3.7]
jvmMain khusus : [com.squareup.okhttp3:okhttp:4.12.0, io.ktor:ktor-client-okhttp:2.3.7, org.postgresql:postgresql:42.6.0]Mari kita bedah bagian kunci dari kode di atas:
data class Artifact: model untuk satu library—group:name:version, daftar target yang kompatibel, dan daftar dependency transitif.notation()mengembalikan string Maven standar.put(Artifact(...)): populasi katalog dengan 11 library.kotlinx-serialization-jsonmemuatkotlinx-serialization-coresecara transitif—sama seperti di dunia nyata.DependencyResolver.resolve(sourceSet, deps): BFS sederhana yang menelusuri dependency transitif. Bila library A membutuhkan B, B ditambahkan ke queue. Hasilnya adalah himpunan lengkap artifact di classpath.- Empat daftar input:
commonDepsuntukcommonMain, laluandroidDeps,iosDeps,jvmDepsditambahcommonDepskarena platform source set mewarisicommonMain. - Output
commonMain: 6 artifact—5 library KMP plusktor-client-coreyang ditarik transitif dariktor-client-content-negotiation. - Output
androidMain: 9 artifact—6 daricommonMainplusokhttp,ktor-client-okhttp, danlifecycle-viewmodel. - Output
iosMain: 7 artifact—6 daricommonMainplusktor-client-darwin. Tidak adaokhttpkarena iOS tidak memakainya. - Output
jvmMain: 9 artifact—sama denganandroidMainkecualilifecycle-viewmodeldigantipostgresql. Inilah inti pemisahan platform source set.
Aturan Memilih Dependency commonMain #
Library yang ditambahkan ke commonMain harus menyatakan dukungan KMP secara eksplisit di dokumentasi. Tiga ciri library KMP: artifact di Maven Central memiliki classifier -jvm, -android, -iosarm64, -linuxx64; build.gradle.kts library memakai plugin kotlin("multiplatform"); dan README menyebutkan dukungan KMP. Library tanpa ciri ini hanya boleh ditambahkan ke source set spesifik.
Referensi Lanjutan #
- Discover KMP project
— panduan deklarasi dependensi multiplatform pada
commonMaindan*Main - Hierarchy template — dokumentasi hierarchy template yang memengaruhi resolusi dependensi
Penutup #
Manajemen dependency KMP adalah kelanjutan langsung dari pola source set—library commonMain harus kompatibel KMP; library platform-specific ditambahkan ke source set spesifik. Resolusi transitif dikelola otomatis oleh Gradle, tetapi pemahaman struktur classpath per source set penting untuk debugging error kompilasi. Chapter berikutnya akan masuk ke pengujian KMP: bagaimana commonTest memungkinkan kita menulis test sekali yang dijalankan di setiap target.