接入平台相关 API

The expect/actual feature is in Beta. It is almost stable, but migration steps may be required in the future. We'll do our best to minimize any changes you will have to make.

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>

Use expected and actual declarations only for Kotlin declarations that have platform-specific dependencies. It is better to implement as much functionality as possible in the shared module even if doing so takes more time.

Don’t overuse expected and actual declarations – in some cases, an interface may be a better choice because it is more flexible and easier to test.