commonMain tidak boleh langsung memanggil API platform seperti java.io.File atau UIKit. Namun, banyak logika bisnis membutuhkan akses ke resource platform—file konfigurasi, jam sistem, UUID, atau logging. Mekanisme expect/actual menjadi jembatan resmi: kita mendeklarasikan kontrak expect di commonMain, lalu menyediakan implementasi actual di setiap source set spesifik. Kompilator Kotlin memverifikasi bahwa setiap expect memiliki actual dengan tanda tangan yang cocok untuk setiap target. Pada chapter ini kita akan membedah tiga jenis deklarasi expect—fungsi, property, dan class—serta melihat simulasi tiga implementasi platform berbeda yang dipilih otomatis oleh kompilator.

Bentuk Dasar expect/actual #

Ada tiga bentuk deklarasi yang umum dipakai. Pertama, expect fun untuk fungsi top-level—misalnya expect fun uuid(): String. Kedua, expect val/expect class untuk deklarasi nilai atau tipe. Ketiga, expect class untuk class dengan constructor dan method—misalnya expect class WaktuSekarang { fun iso8601(): String }.

kotlin commonMain: deklarasi expect
expect fun namaPlatform(): String

expect class WaktuSekarang() {
    fun iso8601(): String
}

Setiap source set spesifik harus menyediakan actual yang sesuai. Kompilator memeriksa bahwa signature actual cocok dengan expect—jumlah parameter, tipe kembalian, dan visibilitas harus sama persis.

kotlin jvmMain: implementasi actual
actual fun namaPlatform(): String = "JVM (OpenJDK 21)"

actual class WaktuSekarang {
    private val instan = java.time.Instant.parse("2026-01-15T10:30:00Z")
    actual fun iso8601(): String = instan.toString()
}

Simulasi expect/actual di Tiga Target #

Karena kompilator JVM tunggal tidak mendukung keyword expect/actual tanpa plugin KMP, demo berikut mensimulasikan pola tersebut dengan interface dan tiga implementasi. Pola yang ditampilkan—kontrak tunggal dengan tiga implementasi—adalah inti dari mekanisme yang sebenarnya dijalankan oleh kompilator KMP.

kotlin
// expect_dan_actual.kt
// Mekanisme expect/actual: deklarasi di commonMain, implementasi per platform.
// Demo disimulasikan dengan interface karena kompilator JVM tunggal
// tidak mendukung keyword expect/actual tanpa plugin KMP.

// --- commonMain: deklarasi kontrak (simulasi expect) ---
// Di proyek KMP nyata, baris ini ditulis sebagai:
//   expect fun namaPlatform(): String
//   expect class WaktuSekarang { fun iso8601(): String }
interface PlatformBindings {
    fun namaPlatform(): String
    fun waktuIso8601(): String
}

// Helper umum di commonMain yang memakai expect
fun sapa(nama: String, bindings: PlatformBindings): String =
    "Halo $nama dari ${bindings.namaPlatform()}"

// --- Simulasi actual untuk berbagai platform ---
// Di proyek nyata, setiap blok ini berada di file .kt terpisah
// pada source set yang berbeda (jvmMain, androidMain, iosMain).

class JvmBindings : PlatformBindings {
    override fun namaPlatform(): String = "JVM (OpenJDK 21)"
    override fun waktuIso8601(): String {
        // Di JVM nyata: java.time.Instant.now() atau OffsetDateTime
        return "2026-01-15T10:30:00Z"
    }
}

class AndroidBindings : PlatformBindings {
    override fun namaPlatform(): String = "Android 14 (API 34)"
    override fun waktuIso8601(): String {
        // Di Android nyata: java.text.SimpleDateFormat di zona Asia/Jakarta
        return "2026-01-15T10:30:00+07:00"
    }
}

class IosBindings : PlatformBindings {
    override fun namaPlatform(): String = "iOS 17.2"
    override fun waktuIso8601(): String {
        // Di iOS nyata: ISO8601DateFormatter via interop Foundation
        return "2026-01-15T10:30:00Z"
    }
}

// --- Wrapper class (simulasi expect class WaktuSekarang) ---
// Di KMP nyata, expect class di commonMain; di sini kita pakai
// delegasi ke PlatformBindings yang dipilih saat konstruksi.
class WaktuSekarang(private val bindings: PlatformBindings) {
    fun iso8601(): String = bindings.waktuIso8601()
}

fun main() {
    println("=== Demo expect/actual di tiga target ===\n")

    val daftarBindings: List<Pair<String, PlatformBindings>> = listOf(
        "Jvm" to JvmBindings(),
        "Android" to AndroidBindings(),
        "Ios" to IosBindings()
    )

    for ((label, bindings) in daftarBindings) {
        val waktu = WaktuSekarang(bindings)
        println("Target      : $label")
        println("Platform    : ${bindings.namaPlatform()}")
        println("Sapa Budi   : ${sapa("Budi", bindings)}")
        println("Waktu ISO   : ${waktu.iso8601()}")
        println()
    }

    println("=== Verifikasi sapa() mengikuti pola commonMain ===")
    val nama = "Siti"
    for ((label, bindings) in daftarBindings) {
        println("sapa(\"$nama\", $label) = ${sapa(nama, bindings)}")
    }
}

Output yang Dihasilkan:

text
=== Demo expect/actual di tiga target ===

Target      : Jvm
Platform    : JVM (OpenJDK 21)
Sapa Budi   : Halo Budi dari JVM (OpenJDK 21)
Waktu ISO   : 2026-01-15T10:30:00Z

Target      : Android
Platform    : Android 14 (API 34)
Sapa Budi   : Halo Budi dari Android 14 (API 34)
Waktu ISO   : 2026-01-15T10:30:00+07:00

Target      : Ios
Platform    : iOS 17.2
Sapa Budi   : Halo Budi dari iOS 17.2
Waktu ISO   : 2026-01-15T10:30:00Z

=== Verifikasi sapa() mengikuti pola commonMain ===
sapa("Siti", Jvm) = Halo Siti dari JVM (OpenJDK 21)
sapa("Siti", Android) = Halo Siti dari Android 14 (API 34)
sapa("Siti", Ios) = Halo Siti dari iOS 17.2

Mari kita bedah bagian kunci dari kode di atas:

  1. interface PlatformBindings: simulasi deklarasi expect di commonMain. Di proyek nyata, baris ini berbentuk expect fun namaPlatform(): String dan expect class WaktuSekarang. Di demo, kita pakai satu interface agar tetap dapat dikompilasi dengan kotlinc JVM.
  2. fun sapa(nama, bindings): helper di commonMain yang memakai kontrak. Fungsi ini tidak tahu platform mana yang aktif—hanya tahu bahwa PlatformBindings punya namaPlatform().
  3. JvmBindings, AndroidBindings, IosBindings: tiga actual implementasi. Di proyek nyata, ketiganya berada di file terpisah pada source set berbeda dan dihubungkan otomatis oleh kompilator.
  4. class WaktuSekarang: simulasi expect class. Constructor menerima bindings yang dipilih saat runtime. Di KMP nyata, kompilator memilih implementasi actual saat kompilasi per target.
  5. main() loop tiga target: menunjukkan output yang sama secara struktural—sapa() dan waktuIso8601()—dengan teks platform yang berbeda sesuai actual. Inilah perilaku yang dihasilkan kompilator KMP saat memilih actual untuk setiap target.

Aturan Signature expect dan actual #

Kompilator KMP menerapkan aturan ketat untuk pencocokan expect/actual. Tanda tangan harus identik—jumlah parameter, tipe, urutan, dan modifier suspend harus sama. Visibilitas default adalah public, tetapi dapat diubah ke internal selama sama di kedua sisi. actual tidak boleh menambahkan default value pada parameter yang tidak ada di expect.

kotlin commonMain: kontrak
expect fun bacaKonfigurasi(nama: String, wajib: Boolean): String
kotlin androidMain: actual yang valid
actual fun bacaKonfigurasi(nama: String, wajib: Boolean): String {
    // pakai Context.getAssets().open(nama)
    return "isi konfigurasi"
}

Bila actual menambahkan parameter default: String = "x", kompilator menolak. Aturan ini menjaga agar pemanggil di commonMain tidak bergantung pada detail implementasi platform tertentu.

expect/actual untuk Class #

expect class adalah kasus khusus. Kita dapat mendeklarasikan expect class WaktuSekarang() dengan constructor tanpa body, dan actual class WaktuSekarang() di source set spesifik dengan constructor yang memanggil API platform. Kode commonMain cukup memanggil WaktuSekarang()—kompilator memilih constructor actual yang sesuai untuk setiap target.

kotlin commonMain: expect class
expect class UuidGenerator() {
    fun baru(): String
}
kotlin iosMain: actual class
actual class UuidGenerator {
    actual fun baru(): String = Foundation.NSUUID().UUIDString()
}

Pendekatan ini memungkinkan commonMain memakai class dengan API platform yang berbeda sama sekali di setiap target—selama signature publiknya sama.

Referensi Lanjutan #

Penutup #

expect/actual adalah jembatan resmi antara commonMain dan platform source sets. Kontrak ditulis sekali di commonMain; implementasi disediakan per platform. Kompilator memverifikasi kesesuaian signature, sehingga kode commonMain dapat memanggil fungsi dan class platform tanpa peduli target mana yang sedang dikompilasi. Chapter berikutnya akan masuk ke pola umum kode platform-specific: kapan sebuah fungsi harus pindah ke source set spesifik, dan bagaimana memilih antara expect/actual versus injeksi interface.