Kotlin Help

Connect to platform-specific APIs

If you’re developing a multiplatform application that needs to access platform-specific APIs that implement the required functionality, use the Kotlin mechanism of expected and actual declarations.

With this mechanism, a common source set defines an expected declaration, and platform source sets must provide the actual declaration that corresponds to the expected declaration. This works for most Kotlin declarations, such as functions, classes, interfaces, enumerations, properties, and annotations.

Expect and actual declarations
// Common expect fun randomUUID(): String
// Android import java.util.* actual fun randomUUID() = UUID.randomUUID().toString()
// iOS import platform.Foundation.NSUUID actual fun randomUUID(): String = NSUUID().UUIDString()

Here's another example of code sharing and interaction between the common and platform logic in a minimalistic logging framework.

// Common enum class LogLevel { DEBUG, WARN, ERROR } internal expect fun writeLogMessage(message: String, logLevel: LogLevel) fun logDebug(message: String) = writeLogMessage(message, LogLevel.DEBUG) fun logWarn(message: String) = writeLogMessage(message, LogLevel.WARN) fun logError(message: String) = writeLogMessage(message, LogLevel.ERROR)
// JVM internal actual fun writeLogMessage(message: String, logLevel: LogLevel) { println("[$logLevel]: $message") }

For JavaScript, a completely different set of APIs is available, and the actual declaration will look like this.

// JS internal actual fun writeLogMessage(message: String, logLevel: LogLevel) { when (logLevel) { LogLevel.DEBUG -> console.log(message) LogLevel.WARN -> console.warn(message) LogLevel.ERROR -> console.error(message) } }

Rules for expected and actual declarations

The main rules regarding expected and actual declarations are:

  • An expected declaration is marked with the expect keyword; the actual declaration is marked with the actual keyword.

  • expect and actual declarations have the same name and are located in the same package (have the same fully qualified name).

  • expect declarations never contain any implementation code and are abstract by default.

  • In interfaces, functions in expect declarations cannot have bodies, but their actual counterparts can be non-abstract and have a body. It allows the inheritors not to implement a particular function.

    To indicate that common inheritors don't need to implement a function, mark it as open. All its actual implementations will be required to have a body:

    // Common expect interface Mascot { open fun display(): String } class MascotImpl : Mascot { // it's ok not to implement `display()`: all `actual`s are guaranteed to have a default implementation } // Platform-specific actual interface Mascot { actual fun display(): String { TODO() } }

During each platform compilation, the compiler ensures that every declaration marked with the expect keyword in the common or intermediate source set has the corresponding declarations marked with the actual keyword in all platform source sets. The IDE provides tools that help you create the missing actual declarations.

If you have a platform-specific library that you want to use in shared code while providing your own implementation for another platform, you can provide a typealias to an existing class as the actual declaration:

expect class AtomicRef<V>(value: V) { fun get(): V fun set(value: V) fun getAndSet(value: V): V fun compareAndSet(expect: V, update: V): Boolean }
actual typealias AtomicRef<V> = java.util.concurrent.atomic.AtomicReference<V>
Last modified: 24 November 2021