Mechanizm expect/actual dla API platform
Deklaruj oczekiwane deklaracje we wspólnym kodzie i dostarczaj implementacje actual dla poszczególnych platform.
Mechanizm expect/actual dla API platform to bezpłatna lekcja Kotlin Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Kotlin Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Kotlin Academy zawiera 4 lekcji w sumie.
Wzorzec expect/actual
expect deklaruje kontrakt interfejsu API w commonMain. Każda platforma udostępnia implementację actual. Kompilator wymaga, aby wszystkie targety miały implementację actual.
// commonMain:
expect fun platformName(): String
// androidMain:
actual fun platformName(): String = "Android ${android.os.Build.VERSION.SDK_INT}"
// iosMain:
actual fun platformName(): String = UIDevice.currentDevice.systemName()expect class
expect class deklaruje klasę wraz z jej interfejsem API w commonMain. Każda platforma udostępnia pełną implementację.
// commonMain:
expect class DateTime {
fun format(pattern: String): String
val timestamp: Long
}
// androidMain:
actual class DateTime {
actual val timestamp = System.currentTimeMillis()
actual fun format(pattern: String): String =
java.text.SimpleDateFormat(pattern).format(java.util.Date(timestamp))
}expect object
expect object deklaruje singleton wraz z jego kontraktem. Jest przydatny w przypadku narzędzi specyficznych dla platformy, takich jak rejestrowanie, ustawienia czy analityka.
// commonMain:
expect object Logger {
fun debug(msg: String)
fun error(msg: String, cause: Throwable? = null)
}
// androidMain:
actual object Logger {
actual fun debug(msg: String) = android.util.Log.d("App", msg)
actual fun error(msg: String, cause: Throwable?) =
android.util.Log.e("App", msg, cause)
}expect val / fun na najwyższym poziomie
Elementy expect val i expect fun na najwyższym poziomie są proste i dobrze sprawdzają się w bezstanowych narzędziach platformowych.
// commonMain:
expect val isDebugBuild: Boolean
expect fun generateUUID(): String
// androidMain:
actual val isDebugBuild: Boolean = BuildConfig.DEBUG
actual fun generateUUID(): String = java.util.UUID.randomUUID().toString()
// iosMain:
actual val isDebugBuild: Boolean = false // or a native flag
actual fun generateUUID(): String = NSUUID().UUIDString()expect fun z implementacją domyślną
Od wersji Kotlin 1.9+ element expect fun może mieć implementację domyślną za pośrednictwem expect fun f() { }. Platformy mogą ją zastąpić lub użyć implementacji domyślnej.
// commonMain (Kotlin 1.9+):
expect fun log(msg: String) {
println("[DEFAULT] $msg") // used if no actual overrides
}
// androidMain:
actual fun log(msg: String) = android.util.Log.d("KMP", msg)
// iosMain (uses default):
// no actual needed — default is usedActual Typealias
Jeśli platforma ma już dokładnie taką klasę, jakiej potrzebujesz, użyj actual typealias, aby się do niej odwołać zamiast tworzyć opakowanie.
// commonMain:
expect class AtomicInt {
fun get(): Int
fun incrementAndGet(): Int
}
// androidMain / jvmMain:
actual typealias AtomicInt = java.util.concurrent.atomic.AtomicInteger
// iosMain:
actual class AtomicInt {
private var value = 0
actual fun get() = value
actual fun incrementAndGet() = ++value
}expect interface (wzorzec pośredni)
Aby uzyskać większą elastyczność, zadeklaruj interfejs w commonMain i użyj expect/actual wyłącznie dla fabryki tworzącej instancję platformową.
// commonMain:
interface FileStorage {
suspend fun readFile(path: String): ByteArray
suspend fun writeFile(path: String, data: ByteArray)
}
expect fun createFileStorage(): FileStorage
// androidMain:
actual fun createFileStorage(): FileStorage = AndroidFileStorage()
// iosMain:
actual fun createFileStorage(): FileStorage = IosFileStorage()Testowanie z expect/actual
W commonTest należy testować wspólny interfejs API. Implementacje actual są uruchamiane podczas zadań testowych specyficznych dla platformy (testDebugUnitTest na Androidzie, iosX64Test na iOS).
// commonTest:
import kotlin.test.Test
import kotlin.test.assertNotNull
class PlatformTest {
@Test
fun testUUID() {
val uuid = generateUUID()
assertNotNull(uuid)
println("UUID: $uuid")
}
}Typowe problemy
Każda zadeklarowana konstrukcja expect musi mieć konstrukcję actual we WSZYSTKICH skonfigurowanych targetach, w przeciwnym razie kompilacja się nie powiedzie. Brakujące implementacje actual są wykrywane podczas kompilacji.
// If you add a new target (e.g., jvmMain), you must add:
// actual fun platformName(): String = "JVM"
// Otherwise:
// error: Expected function platformName has no actual declaration in module :shared for JVM
fun main() { println("Missing actuals = compile error") }Expect Annotation Class
expect annotation class umożliwia mapowanie adnotacji specyficznych dla platformy — na przykład mapowanie wspólnej adnotacji podobnej do @Parcelize na wtyczkę Android Parcelize.
// commonMain:
expect annotation class CommonParcelize()
// androidMain:
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
actual annotation class CommonParcelize
// iosMain:
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
actual annotation class CommonParcelizePraktyczny przykład: Keychain/Preferences
Typowym praktycznym zastosowaniem expect/actual jest interfejs bezpiecznego przechowywania danych, w którym Android używa EncryptedSharedPreferences, a iOS — Keychain.
// commonMain:
expect class SecureStorage {
fun save(key: String, value: String)
fun get(key: String): String?
}
// androidMain:
actual class SecureStorage {
private val prefs = /* EncryptedSharedPreferences */ mapOf<String,String>()
actual fun save(key: String, value: String) { /* ... */ }
actual fun get(key: String): String? = null // simplified
}Szybkie sprawdzenie
Co dzieje się podczas kompilacji, jeśli deklaracja expect nie ma konstrukcji actual dla danego targetu?
Podsumowanie
expect deklaruje interfejs API w commonMain. actual udostępnia implementację platformową. Używaj actual typealias, gdy platforma ma już wymagany typ. Brakujące implementacje actual są wykrywane podczas kompilacji — nie ma niespodzianek w czasie działania.
Często zadawane pytania
Czy lekcja „Mechanizm expect/actual dla API platform” jest bezpłatna?
Tak — pełny tekst „Mechanizm expect/actual dla API platform” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Kotlin Academy, przejdź na CoddyKit PRO. Kurs Kotlin Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Mechanizm expect/actual dla API platform”?
Deklaruj oczekiwane deklaracje we wspólnym kodzie i dostarczaj implementacje actual dla poszczególnych platform. Ćwiczysz Kotlin Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Kotlin Academy?
Nie wymagamy żadnego doświadczenia. Kotlin Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.
Ile czasu zajmuje lekcja „Mechanizm expect/actual dla API platform”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Kotlin Academy?
Tak. Każda lekcja Kotlin Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Struktura projektu KMP: commonMain, androidMain, iosMain
- Mechanizm expect/actual dla API platform
- Współdzielenie warstw Repository i Use Case
- Wstrzykiwanie zależności w KMP za pomocą Koin