expect/actual, ortak kodda platforma bağımlı API’leri bildirmeye izin veren bir Kotlin Multiplatform mekanizmasıdır. expect anahtar kelimesi commonMain içinde bir işlev, sınıf veya özelliğin sözleşmesini oluştururken, actual anahtar kelimesi her platform için somut bir uygulama sağlar. Derleyici, her expect bildiriminin tüm hedef platformlarda karşılık gelen bir actual uygulamasına sahip olduğunu doğrular. JetBrains, 2025’e göre, bu mekanizma platform iş mantığını uygulamak için KMM projelerinin %80’inde kullanılmaktadır.
Önemli Noktalar
expect/actual, platform odaklı programlamayı uygulamak için Kotlin Multiplatform’un bildirimsel bir mekanizmasıdır. Bir API’nin ortak modülde bir kez tanımlanmasını (expect) ve her platform için ayrı ayrı uygulanmasını (actual) sağlar. Arayüzlerin aksine, expect/actual sanal çağrılar oluşturmaz — derleyici, expect ve actual bildirimlerini derleme zamanında birleştirerek dinamik gönderim yükünü ortadan kaldırır.
expect/actual’ın geçmişi, 2017’de Kotlin Multiplatform’un tanıtılmasıyla başladı. Başlangıçta mekanizma expect/actual declarations olarak adlandırılıyordu ve deneyseldi. Kotlin 1.2’de expect ek açıklamaları eklendi ve Kotlin 1.3’te expect/actual sınıflar ve işlevler için kararlı hale geldi. Zamanla mekanizma genişletildi: Kotlin 1.6, companion nesneler için expect/actual desteği ekledi, Kotlin 1.7 enum sınıfları için ve Kotlin 2.0 typealias için.
expect/actual’ın temel özelliği derleme zamanı güvenliğidir. Bir geliştirici commonMain’e bir expect bildirimi ekler ancak iOS için bir actual uygulaması sağlamayı unutursa, derleyici bir hata üretecektir. Bu, yansıma veya platform kodunun dinamik yüklenmesini kullanan yaklaşımlarda yaygın olan çalışma zamanı hatalarını önler.
expect/actual’ın mekanizması source set seviyesinde çalışır — Kotlin Multiplatform’un modül sistemi. Tüm platformlarda kullanılabilen ortak kod, commonMain source set’inde bulunur. Platforma bağımlı kod, iosMain, androidMain, macosMain vb. içinde bulunur. commonMain’deki expect anahtar kelimesi bir API bildirirken, bir platform source set’indeki actual anahtar kelimesi uygulamayı sağlar. Derleyici, kod üretme aşamasında bunları birleştirerek hedef platform için expect işlev çağrısını karşılık gelen actual uygulamasıyla değiştirir.
Tipik bir KMM projesinde source set hiyerarşisi şöyledir: commonMain expect bildirimlerini içerir, iosMain ve androidMain actual uygulamalarını içerir. iOS için derleme yaparken iosMain’deki actual kullanılır; Android için derleme yaparken androidMain’deki actual kullanılır. Source set’ler ara katmanda olabilir (örn. belirli bir mimari için iosArm64Main), bu da farklı cihazlar için uygulamaların iyileştirilmesine olanak tanır.
// commonMain — expect declaration
expect fun getPlatformName(): String
// androidMain — actual for Android
actual fun getPlatformName(): String = "Android"
// iosMain — actual for iOS
actual fun getPlatformName(): String = "iOS"
Kotlin derleyicisi, expect/actual ile çalışırken birkaç koşulu denetler. Her expect bildiriminin, her etkin platform için bir actual uygulaması olmalıdır. Actual bildiriminin imzası, expect imzasıyla eşleşmelidir (@OptionalExpectation ek açıklaması bu gereksinimi gevşetebilir). Erişim değiştiricileri, dönüş türü ve parametreler aynı olmalıdır. Derleyici ayrıca expect ve actual bildirimleri arasında döngüsel bağımlılık olmadığını da denetler.
expect/actual birkaç tür bildirimi destekler. En yaygın kullanılanlar platform işlemleri için expect/actual işlevler, yerel uygulama gerektiren nesneler için expect/actual sınıflar ve sabitler ve ayarlar için expect/actual özelliklerdir. Her türün kendi kullanım kuralları ve sınırlamaları vardır.
Expect/actual işlevler en basit ve en yaygın türdür. Zaman alma, dosya okuma veya HTTP istekleri gönderme gibi platform API’lerini çağırmak için kullanılır. Expect/actual sınıflar, yerel kodla doğrudan etkileşime giren nesneler oluşturmak için kullanılır (örn. kameraya, coğrafi konuma veya anahtar deposuna erişmek için). Expect/actual özellikler (val) platform sabitleri için uygundur — işletim sistemi adı, SDK sürümü veya sistem dizin yolu.
| Bildirim türü | Anahtar kelimeler | Kullanım örneği |
|---|---|---|
| İşlev | expect fun / actual fun | Benzersiz bir cihaz tanımlayıcısı alma |
| Sınıf | expect class / actual class | SecureStorage’a erişme (Keychain / EncryptedSharedPreferences) |
| Özellik | expect val / actual val | Geçerli platform (iOS / Android) |
| Enum sınıfı | expect enum / actual enum | Kullanılabilir uygulama izinleri listesi |
| Typealias | expect typealias / actual typealias | Platforma özgü ağ yanıt türü |
Tüm Kotlin yapıları expect/actual ile kullanılamaz. Bir expect bildirimi bir gövde içeremez — yalnızca bir imza. Bir expect sınıfı, parametreli bir kurucuya sahip olamaz (boş bir birincil kurucusu olmalıdır). enum expect/actual için, tüm sabitler expect ve actual’de aynı olmalıdır. Expect özellikleri val (var değil) olmalıdır, çünkü platform özellikleri için ortak modülde durum depolamak anlamsızdır.
Basit işlevlerden tam sınıflara kadar expect/actual’ın pratik örneklerini keşfedelim. Temel durum, kullanıcı arayüzünde kullanmak için platform adını almaktır. Daha karmaşık örnekler, yerel depolamaya erişim ve platform iş parçacıklarıyla çalışmayı içerir.
// commonMain — expect class for secure storage
expect class PlatformStorage {
fun save(key: String, value: String)
fun get(key: String): String?
fun remove(key: String)
}
// androidMain — actual on 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() }
}
Bu örnekte, expect sınıfı PlatformStorage basit bir anahtar-değer deposunun sözleşmesini tanımlar. Android’de uygulama SharedPreferences kullanırken, iOS’ta Keychain veya NSUserDefaults kullanır. expect/actual sayesinde, commonMain’deki iş mantığı, platform uygulamasını bilmeden save/get/remove çağrısı yapar.
// iosMain — actual on iOS with 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)
}
}
expect/actual API’leri tasarlarken birkaç ilke izlenmelidir. expect bildirimlerinin sayısını en aza indirin — ortak kod ne kadar fazlaysa bakım o kadar kolaydır. expect/actual’ı yalnızca platformlar arasında gerçekten farklılık gösteren API’ler için kullanın. Kodun geri kalanı için, test etmeyi basitleştiren fabrikalar veya bağımlılık enjeksiyonu ile arayüzler kullanın.
expect bildirimlerinin tek bir dosyada karıştırılması yerine tematik modüllere göre gruplandırılması önerilir. Örneğin, depolamayla ilgili expect bildirimleri için Storage.kt, işletim sistemiyle çalışan expect işlevleri için Platform.kt ve analitik expect sınıfları için Analytics.kt. Bu, bir KMM projesinin platform yüzeyinde gezinmeyi ve anlamayı kolaylaştırır. Her actual dosyası, karşılık gelen source set’te bulunmalıdır: androidMain, iosMain, desktopMain vb.
actual’ın ortak kod kullandığı expect fun ile actual fun aracılığıyla varsayılan uygulamalar yaygın bir anti-kalıptır. Platform uygulaması varsayılandan farklı değilse, expect/actual gerekli değildir. Bu gibi durumlarda, commonMain’de basit bir işlev kullanın. Ayrıca önemsiz getter’lar için expect/actual’dan kaçının — sabitlerle expect val kullanın.
expect/actual kodunun uygun yapısı, projenin okunabilirliği için kritiktir. Her expect/actual modülünün tek bir giriş noktası olmalıdır. Örnek organizasyon: commonMain/kotlin/com/project/platform expect bildirimlerini içerir, androidMain/kotlin/com/project/platform Android için actual’ı içerir, iosMain/kotlin/com/project/platform iOS için actual’ı içerir. Dosya ve paket adları, expect ve actual için eşleşmelidir, böylece bir geliştirici karşılık gelen uygulamayı hızla bulabilir.
Platform fabrikasıyla arayüzler, expect/actual’ın ana alternatifidir. Bir expect sınıfı yerine, commonMain’de bir arayüz bildirebilir ve platform modüllerinde somut sınıflar oluşturabilirsiniz. Bir fabrika veya bağımlılık enjeksiyonu kabı, çalışma zamanında doğru uygulamayı sağlar. Bu yaklaşım, arayüz taklit edilebildiği için testler için daha uygundur.
Bağımlılık enjeksiyonu (Koin, Kodein) daha esnek ancak daha az verimli bir yaklaşımdır. Bir DI kabı, her platform için ayrı ayrı yapılandırılır ve ortak koda platform bağımlılıkları sağlar. expect/actual’ın aksine, enjeksiyon çalışma zamanında gerçekleşir ve testler için uygulamaların değiştirilmesine olanak tanır. Öte yandan, DI yapılandırma hataları yalnızca çalışma zamanında tespit edilir, derleme zamanında değil.
| Yaklaşım | Derleme zamanı denetimi | Test esnekliği | Çalışma zamanı yükü |
|---|---|---|---|
| expect/actual | Tam | Düşük (actual taklit edilemez) | Sıfır (derleme zamanı bağlantısı) |
| Arayüzler + Fabrika | Kısmi | Yüksek (taklit edilebilir) | Minimum (sanal çağrı) |
| Bağımlılık enjeksiyonu | Hayır (çalışma zamanı) | Yüksek | Orta (DI proxy’leri) |
expect/actual ve alternatifler arasındaki seçim bağlama bağlıdır. Performans açısından kritik kod (oyun motorları, gerçek zamanlı işleme) için sıfır yük nedeniyle expect/actual tercih edilir. İş mantığı (depolar, kullanım durumları) için test etmeyi basitleştirmek amacıyla DI ile arayüzler kullanmak daha iyidir. Birleşik bir yaklaşım — düşük seviyeli platform işlemleri için expect/actual ve iş mantığı katmanı için arayüzler — üretimdeki KMM projelerinin çoğunda kullanılır.
Sıkça sorulan sorular
expect/actual, sanal çağrılar olmadan derleme zamanında uygulamayı bağlarken, arayüzler çalışma zamanında bağlanır. expect/actual tüm platformlar için uygulama garantisi verir, arayüzler çalışma zamanı denetimleri gerektirir.
Evet, expect enum Kotlin 1.7’den itibaren desteklenmektedir. expect ve actual enum’lardaki tüm sabitler eşleşmelidir. Farklı platformlarda farklı sabit değerleri bir derleme hatasıdır.
Derleyici, actual uygulaması eksik olan her platform için bir hata üretecektir. Tüm expect bildirimleri için karşılık gelen actual uygulamaları eklenene kadar proje derlenmez.
Hayır, expect ve actual farklı source set’lerde olmalıdır. expect commonMain veya bir ara source set’te, actual bir platform source set’inde olmalıdır. expect ve actual’ı aynı source set’e koymak bir derleme hatasıdır.
expect/actual’ı test etmek için, platform test source set’leriyle commonTest kullanın. commonTest’te expect testleri ve her platform için actual testleri yazın. Entegrasyon testleri her hedef platformda ayrı ayrı çalıştırılır.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun