expect/actual — esensi, kata kunci KMM, dan cara kerjanya

Penulis: IT Sectr Diterbitkan: 2026-06-05 Waktu membaca: 8 mnt

expect/actual — mekanisme Kotlin Multiplatform yang memungkinkan pendeklarasian API yang bergantung pada platform dalam kode bersama. Kata kunci expect membuat kontrak fungsi, kelas, atau properti di commonMain, dan kata kunci actual menyediakan implementasi konkret untuk setiap platform. Kompilator memeriksa bahwa setiap deklarasi expect sesuai dengan implementasi actual di semua platform target. Menurut JetBrains, 2025, mekanisme ini digunakan di 80% proyek KMM untuk mengimplementasikan logika bisnis khusus platform.

Poin utama

  • expect — kata kunci untuk mendeklarasikan kontrak fungsi, kelas, atau properti dalam kode bersama.
  • actual — kata kunci untuk menyediakan implementasi platform dari deklarasi expect.
  • commonMain — source set dengan kode bersama tempat deklarasi expect berada.
  • Pemeriksaan kompilator — kompilator menjamin keberadaan implementasi actual untuk semua platform target.
  • Source set — kumpulan (iosMain, androidMain) tempat implementasi platform actual berada.

Apa itu expect/actual?

expect/actual — adalah mekanisme deklaratif Kotlin Multiplatform untuk mengimplementasikan pemrograman berorientasi platform. Ini memungkinkan deskripsi API satu kali di modul bersama (expect) dan implementasinya secara terpisah untuk setiap platform (actual). Berbeda dengan antarmuka, expect/actual tidak membuat panggilan virtual — kompilator menghubungkan deklarasi expect dan actual pada tahap kompilasi, yang menghilangkan overhead pengiriman dinamis.

Sejarah expect/actual dimulai dengan munculnya Kotlin Multiplatform pada tahun 2017. Awalnya mekanisme ini disebut expect/actual declarations dan bersifat eksperimental. Di Kotlin 1.2, anotasi expect ditambahkan, dan di Kotlin 1.3, expect/actual menjadi stabil untuk kelas dan fungsi. Secara bertahap mekanisme ini diperluas: di Kotlin 1.6, dukungan untuk objek companion ditambahkan, di Kotlin 1.7 — untuk kelas enum, dan di Kotlin 2.0 — untuk typealias.

Fitur utama expect/actual — keamanan di tingkat kompilator. Jika pengembang menambahkan deklarasi expect ke commonMain tetapi lupa menyediakan implementasi actual untuk iOS, kompilator akan memberikan kesalahan. Ini mencegah kesalahan runtime yang khas pada pendekatan dengan refleksi atau pemuatan kode platform secara dinamis.

Bagaimana mekanisme expect/actual bekerja

Mekanisme expect/actual bekerja di tingkat source set — sistem modul Kotlin Multiplatform. Kode bersama yang dapat diakses oleh semua platform berada di source set commonMain. Kode yang bergantung pada platform — di iosMain, androidMain, macosMain, dan seterusnya. Kata kunci expect di commonMain mendeklarasikan API, dan kata kunci actual di source set platform menyediakan implementasi. Kompilator menghubungkannya pada tahap pembuatan kode, menggantikan panggilan fungsi expect dengan implementasi actual yang sesuai untuk platform target.

Hierarki source set dalam proyek KMM tipikal terlihat seperti ini: commonMain berisi deklarasi expect, iosMain dan androidMain berisi implementasi actual. Saat kompilasi untuk iOS, actual dari iosMain digunakan; saat kompilasi untuk Android — dari androidMain. Source set dapat bersifat perantara (misalnya, iosArm64Main untuk arsitektur tertentu), yang memungkinkan penyesuaian implementasi untuk perangkat yang berbeda.

kotlin
// commonMain — deklarasi expect
expect fun getPlatformName(): String

// androidMain — actual untuk Android
actual fun getPlatformName(): String = "Android"

// iosMain — actual untuk iOS
actual fun getPlatformName(): String = "iOS"

Pemeriksaan implementasi actual oleh kompilator

Kompilator Kotlin memeriksa beberapa kondisi saat bekerja dengan expect/actual. Setiap deklarasi expect harus memiliki implementasi actual untuk setiap platform aktif. Tanda tangan deklarasi actual harus cocok dengan tanda tangan expect (anotasi @OptionalExpectation dapat melonggarkan persyaratan ini). Pengubah akses, tipe pengembalian, dan parameter harus identik. Kompilator juga memeriksa tidak adanya dependensi siklik antara deklarasi expect dan actual.

Jenis expect/actual: fungsi, kelas, properti

expect/actual mendukung beberapa jenis deklarasi. Yang paling sering digunakan adalah fungsi expect/actual untuk operasi platform, kelas expect/actual untuk objek yang memerlukan implementasi native, dan properti expect/actual untuk konstanta dan pengaturan. Setiap jenis memiliki aturan penggunaan dan batasannya sendiri.

Fungsi expect/actual — jenis yang paling sederhana dan paling umum. Mereka digunakan untuk memanggil API platform seperti mendapatkan waktu, membaca file, atau mengirim permintaan HTTP. Kelas expect/actual diterapkan untuk membuat objek yang berinteraksi langsung dengan kode native (misalnya, untuk akses ke kamera, geolokasi, atau penyimpanan kunci). Properti expect/actual (val) cocok untuk konstanta platform — nama sistem operasi, versi SDK, atau jalur ke direktori sistem.

Jenis deklarasiKata kunciContoh penggunaan
Fungsiexpect fun / actual funMendapatkan ID unik perangkat
Kelasexpect class / actual classAkses ke SecureStorage (Keychain / EncryptedSharedPreferences)
Propertiexpect val / actual valPlatform saat ini (iOS / Android)
Kelas enumexpect enum / actual enumDaftar izin aplikasi yang tersedia
Typealiasexpect typealias / actual typealiasTipe respons jaringan khusus platform

Batasan expect/actual

Tidak semua konstruksi Kotlin dapat digunakan dengan expect/actual. Deklarasi expect tidak boleh berisi tubuh — hanya tanda tangan. Kelas expect tidak boleh memiliki konstruktor dengan parameter (harus memiliki konstruktor utama kosong). Untuk enum expect/actual, semua konstanta harus identik di expect dan actual. Properti expect harus val (bukan var), karena menyimpan status di modul bersama untuk properti platform tidak masuk akal.

Contoh kode: dari sederhana ke kompleks

Mari kita lihat contoh praktis expect/actual dari fungsi sederhana hingga kelas lengkap. Kasus dasar — mendapatkan nama platform untuk digunakan di antarmuka. Contoh yang lebih kompleks mencakup akses ke penyimpanan native dan bekerja dengan thread platform.

kotlin
// commonMain — kelas expect untuk penyimpanan aman
expect class PlatformStorage {
    fun save(key: String, value: String)
    fun get(key: String): String?
    fun remove(key: String)
}

// androidMain — actual di Android
actual class PlatformStorage {
    private val prefs = AppContext.getSharedPreferences("secure", 0)

    actual fun save(key: String, value: String) { prefs.edit().putString(key, value).apply() }
    actual fun get(key: String): String? = prefs.getString(key, null)
    actual fun remove(key: String) { prefs.edit().remove(key).apply() }
}

Dalam contoh ini, kelas expect PlatformStorage mendefinisikan kontrak penyimpanan kunci-nilai sederhana. Di Android, implementasi menggunakan SharedPreferences, dan di iOS — Keychain atau NSUserDefaults. Berkat expect/actual, logika bisnis di commonMain memanggil save/get/remove tanpa mengetahui implementasi platform.

kotlin
// iosMain — actual di iOS dengan Keychain
actual class PlatformStorage {
    actual fun save(key: String, value: String) {
        val query = mapOf<String, Any>(
            kSecClass to kSecClassGenericPassword,
            kSecAttrAccount to key,
            kSecValueData to value.encodeToByteArray()
        )
        SecItemAdd(query, null)
    }

    actual fun get(key: String): String? {
        val query = mapOf<String, Any>(
            kSecClass to kSecClassGenericPassword,
            kSecAttrAccount to key,
            kSecReturnData to true
        )
        val result = mutableMapOf<String, Any>()
        return if (SecItemCopyMatching(query, result) == errSecSuccess)
            result[kSecValueData]?.toString()
        else null
    }

    actual fun remove(key: String) {
        val query = mapOf<String, Any>(
            kSecClass to kSecClassGenericPassword,
            kSecAttrAccount to key
        )
        SecItemDelete(query)
    }
}

Praktik terbaik expect/actual

Saat mendesain API expect/actual, beberapa prinsip harus diikuti. Minimalkan jumlah deklarasi expect — semakin banyak kode bersama, semakin mudah perawatannya. Gunakan expect/actual hanya untuk API yang benar-benar berbeda di setiap platform. Untuk kode lainnya, terapkan antarmuka dengan pabrik atau injeksi dependensi, yang menyederhanakan pengujian.

Disarankan untuk mengelompokkan deklarasi expect berdasarkan modul tematik, bukan mencampurnya dalam satu file. Misalnya, Storage.kt untuk deklarasi expect penyimpanan, Platform.kt untuk fungsi expect bekerja dengan sistem operasi, dan Analytics.kt untuk kelas expect analitik. Ini menyederhanakan navigasi dan pemahaman permukaan platform proyek KMM. Setiap file actual harus berada di source set yang sesuai: androidMain, iosMain, desktopMain, dan seterusnya.

Implementasi default melalui expect fun dengan actual fun, di mana actual menggunakan kode bersama — adalah antipattern yang umum. Jika implementasi platform tidak berbeda dari default, expect/actual tidak diperlukan. Dalam kasus seperti itu, gunakan fungsi sederhana di commonMain. Juga hindari expect/actual untuk getter sepele — gunakan expect val dengan konstanta.

Organisasi kode dalam proyek

Struktur kode expect/actual yang benar sangat penting untuk keterbacaan proyek. Setiap modul expect/actual harus memiliki satu titik masuk. Contoh organisasi: commonMain/kotlin/com/project/platform berisi deklarasi expect, androidMain/kotlin/com/project/platform — actual untuk Android, iosMain/kotlin/com/project/platform — actual untuk iOS. Nama file dan paket harus cocok untuk expect dan actual, sehingga pengembang dapat dengan cepat menemukan implementasi yang sesuai.

Alternatif expect/actual di KMM

Antarmuka dengan pabrik platform — alternatif utama untuk expect/actual. Alih-alih kelas expect, antarmuka dapat dideklarasikan di commonMain dan kelas konkret dibuat di modul platform. Pabrik atau kontainer injeksi dependensi menyediakan implementasi yang benar di runtime. Pendekatan ini lebih baik untuk pengujian karena antarmuka dapat di-mock.

Injeksi Dependensi (Koin, Kodein) — pendekatan yang lebih fleksibel tetapi kurang berkinerja. Kontainer DI dikonfigurasi secara terpisah untuk setiap platform dan menyediakan dependensi platform ke kode bersama. Berbeda dengan expect/actual, injeksi terjadi di runtime, yang memungkinkan penggantian implementasi untuk pengujian. Di sisi lain, kesalahan konfigurasi DI hanya terdeteksi saat dijalankan, bukan pada tahap kompilasi.

PendekatanPemeriksaan di tahap kompilasiFleksibilitas pengujianOverhead runtime
expect/actualPenuhRendah (actual tidak dapat di-mock)Nol (ikatan kompilasi)
Antarmuka + pabrikSebagianTinggi (dapat di-mock)Minimal (panggilan virtual)
Injeksi DependensiTidak (runtime)TinggiSedang (proksi DI)

Pilihan antara expect/actual dan alternatif tergantung pada konteks. Untuk kinerja kritis (mesin game, pemrosesan real-time) expect/actual lebih disukai karena overhead runtime nol. Untuk logika bisnis (repositori, use case) lebih baik menggunakan antarmuka dengan DI untuk menyederhanakan pengujian. Pendekatan gabungan — expect/actual untuk operasi platform tingkat rendah dan antarmuka untuk lapisan logika bisnis — diterapkan di sebagian besar proyek KMM produksi.

Pertanyaan yang sering diajukan

Apa perbedaan antara expect/actual dan antarmuka?

expect/actual menghubungkan implementasi di tahap kompilasi tanpa panggilan virtual, sedangkan antarmuka — di runtime. expect/actual menjamin keberadaan implementasi untuk semua platform, antarmuka memerlukan pemeriksaan runtime.

Bisakah expect/actual digunakan untuk enum?

Ya, expect enum didukung sejak Kotlin 1.7. Semua konstanta di expect dan actual enum harus cocok. Nilai konstanta yang berbeda di platform yang berbeda — kesalahan kompilasi.

Apa yang terjadi jika implementasi actual dilupakan?

Kompilator akan memberikan kesalahan untuk setiap platform yang tidak memiliki implementasi actual. Proyek tidak akan dibangun sampai semua deklarasi expect memiliki implementasi actual yang sesuai.

Bisakah expect/actual digunakan dalam satu source set?

Tidak, expect dan actual harus berada di source set yang berbeda. expect — di commonMain atau source set perantara, actual — di source set platform. Menempatkan expect dan actual di source set yang sama adalah kesalahan kompilasi.

Bagaimana cara menguji kode expect/actual?

Untuk menguji expect/actual, gunakan commonTest dengan source set pengujian platform. Tulis pengujian expect di commonTest dan pengujian actual untuk setiap platform. Pengujian integrasi dijalankan secara terpisah di setiap platform target.

Ringkasan

  • expect/actual — mekanisme kunci Kotlin Multiplatform untuk implementasi platform dengan pemeriksaan kompilator.
  • expect mendeklarasikan kontrak di commonMain, actual menyediakan implementasi di source set platform.
  • Jenis deklarasi mencakup fungsi, kelas, properti, kelas enum, dan typealias dengan aturan penggunaan yang berbeda.
  • Pemeriksaan kompilator menjamin keberadaan implementasi actual untuk semua platform target, mencegah kesalahan runtime.
  • Disarankan untuk meminimalkan expect/actual dan menggunakan antarmuka dengan DI untuk logika bisnis.
  • Organisasi kode harus seragam dengan nama file dan paket yang cocok untuk expect dan actual.
  • Gunakan expect/actual untuk operasi platform tingkat rendah (penyimpanan, sistem file, sensor) — ini menjamin overhead runtime nol.

Kami akan mengembangkan aplikasi seluler turnkey

IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.

Diskusikan proyek

Baca juga