expect/actual — mekanismo ng Kotlin Multiplatform na nagpapahintulot sa pagdedeklara ng mga API na nakadepende sa platform sa shared code. Ang keyword na expect ay lumilikha ng kontrata ng function, klase o property sa commonMain, at ang keyword na actual ay nagbibigay ng konkretong implementasyon para sa bawat platform. Sinusuri ng compiler na ang bawat expect declaration ay may katumbas na actual implementation sa lahat ng target na platform. Ayon sa JetBrains, 2025, ang mekanismong ito ay ginagamit sa 80% ng KMM projects para sa pagpapatupad ng platform-specific na business logic.
Mga pangunahing punto
expect/actual — ay isang deklaratibong mekanismo ng Kotlin Multiplatform para sa pagpapatupad ng platform-oriented programming. Pinapayagan nito ang paglalarawan ng API nang isang beses sa shared module (expect) at pagpapatupad nito nang hiwalay para sa bawat platform (actual). Hindi tulad ng mga interface, ang expect/actual ay hindi lumilikha ng virtual na tawag — ikinokonekta ng compiler ang expect at actual declarations sa compilation phase, na nag-aalis ng overhead ng dynamic dispatch.
Nagsimula ang kasaysayan ng expect/actual sa pagdating ng Kotlin Multiplatform noong 2017. Sa simula, ang mekanismo ay tinawag na expect/actual declarations at eksperimental. Sa Kotlin 1.2, idinagdag ang expect annotations, at sa Kotlin 1.3, naging stable ang expect/actual para sa mga klase at function. Unti-unting lumawak ang mekanismo: sa Kotlin 1.6, idinagdag ang suporta para sa companion objects, sa Kotlin 1.7 — para sa enum classes, at sa Kotlin 2.0 — para sa typealias.
Ang pangunahing katangian ng expect/actual — kaligtasan sa antas ng compiler. Kung ang isang developer ay nagdagdag ng expect declaration sa commonMain ngunit nakalimutang magbigay ng actual implementation para sa iOS, ang compiler ay magbibigay ng error. Ito ay pumipigil sa runtime errors na katangian ng mga approach na may reflection o dynamic loading ng platform code.
Ang mekanismo ng expect/actual ay gumagana sa antas ng source set — ang module system ng Kotlin Multiplatform. Ang shared code na accessible sa lahat ng platform ay matatagpuan sa source set na commonMain. Ang platform-dependent code — sa iosMain, androidMain, macosMain at iba pa. Ang keyword na expect sa commonMain ay nagdedeklara ng API, at ang keyword na actual sa platform source set ay nagbibigay ng implementasyon. Ikinokonekta sila ng compiler sa code generation phase, na pinapalitan ang tawag sa expect function ng katumbas na actual implementation para sa target na platform.
Ang source set hierarchy sa isang tipikal na KMM project ay ganito: commonMain ay naglalaman ng expect declarations, iosMain at androidMain ay naglalaman ng actual implementations. Sa compilation para sa iOS, ginagamit ang actual mula sa iosMain; sa compilation para sa Android — mula sa androidMain. Ang source sets ay maaaring intermediate (halimbawa, iosArm64Main para sa isang partikular na architecture), na nagpapahintulot sa pagpino ng implementations para sa iba’t ibang devices.
// commonMain — expect declaration
expect fun getPlatformName(): String
// androidMain — actual para sa Android
actual fun getPlatformName(): String = "Android"
// iosMain — actual para sa iOS
actual fun getPlatformName(): String = "iOS"
Ang Kotlin compiler ay sumusuri ng ilang kundisyon kapag nagtatrabaho sa expect/actual. Bawat expect declaration ay dapat may actual implementation para sa bawat aktibong platform. Ang signature ng actual declaration ay dapat tumugma sa signature ng expect (@OptionalExpectation annotation ay maaaring magpaluwag sa requirement na ito). Ang access modifiers, return type at parameters ay dapat magkapareho. Sinusuri rin ng compiler ang kawalan ng cyclic dependencies sa pagitan ng expect at actual declarations.
expect/actual ay sumusuporta sa ilang uri ng deklarasyon. Ang pinakamadalas gamitin ay expect/actual functions para sa platform operations, expect/actual classes para sa mga bagay na nangangailangan ng native implementation, at expect/actual properties para sa constants at settings. Bawat uri ay may kanya-kanyang rules at limitasyon.
Expect/actual functions — ang pinakasimple at pinakakaraniwang uri. Ginagamit ang mga ito para sa pagtawag ng platform APIs tulad ng pagkuha ng oras, pagbabasa ng file, o pagpapadala ng HTTP requests. Expect/actual classes ay inilalapat para sa paglikha ng mga bagay na direktang nakikipag-ugnayan sa native code (halimbawa, para sa access sa camera, geolocation o key storage). Expect/actual properties (val) ay angkop para sa platform constants — pangalan ng operating system, bersyon ng SDK o path sa system directory.
| Uri ng deklarasyon | Mga keyword | Halimbawa ng paggamit |
|---|---|---|
| Function | expect fun / actual fun | Pagkuha ng unique device ID |
| Klase | expect class / actual class | Access sa SecureStorage (Keychain / EncryptedSharedPreferences) |
| Property | expect val / actual val | Kasalukuyang platform (iOS / Android) |
| Enum class | expect enum / actual enum | Listahan ng mga available na app permissions |
| Typealias | expect typealias / actual typealias | Uri ng network response na specific sa platform |
Hindi lahat ng Kotlin constructs ay maaaring gamitin sa expect/actual. Ang expect declaration ay hindi maaaring maglaman ng body — signature lamang. Ang expect class ay hindi maaaring magkaroon ng constructor na may parameters (dapat may empty primary constructor). Para sa enum expect/actual, lahat ng constants ay dapat magkapareho sa expect at actual. Ang expect properties ay dapat val (hindi var), dahil ang pag-imbak ng estado sa shared module para sa platform properties ay walang saysay.
Suriin natin ang mga praktikal na halimbawa ng expect/actual mula simpleng function hanggang ganap na klase. Ang base case — pagkuha ng platform name para gamitin sa UI. Ang mas komplikadong halimbawa ay may kasamang access sa native storage at pagtatrabaho sa platform threads.
// commonMain — expect class para sa secure storage
expect class PlatformStorage {
fun save(key: String, value: String)
fun get(key: String): String?
fun remove(key: String)
}
// androidMain — actual sa 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() }
}
Sa halimbawang ito, ang expect class na PlatformStorage ay tumutukoy sa kontrata ng isang simpleng key-value storage. Sa Android, ang implementasyon ay gumagamit ng SharedPreferences, at sa iOS — Keychain o NSUserDefaults. Dahil sa expect/actual, ang business logic sa commonMain ay tumatawag ng save/get/remove nang hindi alam ang platform implementation.
// iosMain — actual sa iOS na may 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)
}
}
Sa pagdidisenyo ng expect/actual API, dapat sundin ang ilang prinsipyo. I-minimize ang bilang ng expect declarations — mas maraming shared code, mas madali ang maintenance. Gamitin ang expect/actual para lamang sa mga API na talagang naiiba sa mga platform. Para sa natitirang code, mag-apply ng interfaces na may factories o dependency injection, na nagpapasimple ng testing.
Inirerekomenda na pangkatin ang expect declarations ayon sa thematic modules, sa halip na paghaluin ang mga ito sa isang file. Halimbawa, Storage.kt para sa expect declarations ng storage, Platform.kt para sa expect functions na may kinalaman sa operating system, at Analytics.kt para sa expect classes ng analytics. Nagpapasimple ito ng navigation at pag-unawa sa platform surface ng KMM project. Bawat actual file ay dapat nasa kaukulang source set: androidMain, iosMain, desktopMain at iba pa.
Default implementations sa pamamagitan ng expect fun na may actual fun, kung saan ang actual ay gumagamit ng shared code — ay isang karaniwang antipattern. Kung ang platform implementation ay hindi naiiba mula sa default, hindi kailangan ang expect/actual. Sa ganitong mga kaso, gumamit ng simpleng function sa commonMain. Iwasan din ang expect/actual para sa trivial getters — gumamit ng expect val na may constants.
Ang tamang istraktura ng expect/actual code ay kritikal para sa pagiging nababasa ng proyekto. Bawat expect/actual module ay dapat may iisang entry point. Halimbawa ng organisasyon: commonMain/kotlin/com/project/platform ay naglalaman ng expect declarations, androidMain/kotlin/com/project/platform — actual para sa Android, iosMain/kotlin/com/project/platform — actual para sa iOS. Ang mga pangalan ng file at package ay dapat magkatugma para sa expect at actual, upang mabilis na makita ng developer ang kaukulang implementasyon.
Mga Interface na may platform factory — ang pangunahing alternatibo sa expect/actual. Sa halip na expect class, maaaring magdeklara ng interface sa commonMain at gumawa ng concrete classes sa platform modules. Ang factory o dependency injection container ay nagbibigay ng tamang implementasyon sa runtime. Ang approach na ito ay mas angkop para sa testing dahil ang interface ay maaaring i-mock.
Dependency Injection (Koin, Kodein) — isang mas flexible ngunit hindi gaanong performant na approach. Ang DI container ay naka-configure nang hiwalay para sa bawat platform at nagbibigay ng platform dependencies sa shared code. Hindi tulad ng expect/actual, ang injection ay nangyayari sa runtime, na nagpapahintulot sa pagpapalit ng implementations para sa testing. Sa kabilang banda, ang mga error sa DI configuration ay natutuklasan lamang sa pag-start, hindi sa compilation phase.
| Approach | Pagsusuri sa compilation phase | Flexibility ng testing | Runtime overhead |
|---|---|---|---|
| expect/actual | Buong | Mababa (actual hindi maaaring i-mock) | Wala (compilation binding) |
| Interface + factory | Bahagya | Mataas (maaaring i-mock) | Minimal (virtual call) |
| Dependency Injection | Hindi (runtime) | Mataas | Katamtaman (DI proxy) |
Ang pagpili sa pagitan ng expect/actual at mga alternatibo ay depende sa konteksto. Para sa critical performance (game engine, real-time processing) ang expect/actual ay mas mainam dahil sa zero runtime overhead. Para sa business logic (repositories, use cases) mas mainam na gumamit ng interfaces na may DI para pasimplehin ang testing. Ang combined approach — expect/actual para sa low-level platform operations at interfaces para sa business logic layer — ay inilalapat sa karamihan ng production KMM projects.
Mga madalas itanong
expect/actual ay nagkokonekta ng implementasyon sa compilation phase nang walang virtual calls, habang ang interfaces — sa runtime. Ginagarantiya ng expect/actual ang pagkakaroon ng implementasyon para sa lahat ng platform, ang interfaces ay nangangailangan ng runtime checks.
Oo, expect enum ay suportado mula noong Kotlin 1.7. Lahat ng constants sa expect at actual enum ay dapat magkatugma. Iba’t ibang values ng constants sa iba’t ibang platform — compilation error.
Ang compiler ay magbibigay ng error para sa bawat platform kung saan nawawala ang actual implementation. Hindi mabubuo ang project hanggang sa lahat ng expect declarations ay may kaukulang actual implementations.
Hindi, expect at actual ay dapat nasa iba’t ibang source sets. expect — sa commonMain o intermediate source set, actual — sa platform source set. Ang paglalagay ng expect at actual sa parehong source set ay compilation error.
Para sa testing ng expect/actual, gamitin ang commonTest na may platform test source sets. Sumulat ng expect tests sa commonTest at actual tests para sa bawat platform. Ang integration tests ay pinapatakbo nang hiwalay sa bawat target na platform.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din