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/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.
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.
// 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"
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.
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 deklarasi | Kata kunci | Contoh penggunaan |
|---|---|---|
| Fungsi | expect fun / actual fun | Mendapatkan ID unik perangkat |
| Kelas | expect class / actual class | Akses ke SecureStorage (Keychain / EncryptedSharedPreferences) |
| Properti | expect val / actual val | Platform saat ini (iOS / Android) |
| Kelas enum | expect enum / actual enum | Daftar izin aplikasi yang tersedia |
| Typealias | expect typealias / actual typealias | Tipe respons jaringan khusus platform |
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.
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.
// 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.
// 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)
}
}
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.
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.
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.
| Pendekatan | Pemeriksaan di tahap kompilasi | Fleksibilitas pengujian | Overhead runtime |
|---|---|---|---|
| expect/actual | Penuh | Rendah (actual tidak dapat di-mock) | Nol (ikatan kompilasi) |
| Antarmuka + pabrik | Sebagian | Tinggi (dapat di-mock) | Minimal (panggilan virtual) |
| Injeksi Dependensi | Tidak (runtime) | Tinggi | Sedang (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
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.
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.
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.
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.
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
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.
Baca juga