接入平台相关 API
The
expect/actualfeature 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 (for example, generating a UUID), 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.

The compiler ensures that every declaration marked with the expect keyword in the common module has the corresponding declarations marked with the actual keyword in all platform modules. The IDE provides tools that help you create the missing actual declarations.
Use expected and actual declarations only for Kotlin declarations that have platform-specific dependencies. Implementing as much functionality as possible in the shared module is better, 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.
Learn how to add dependencies on platform-specific libraries.
Examples
For simplicity, the following examples use intuitive target names, like iOS and Android. However, in your Gradle build files, you need to use a specific target name from the list of supported targets.
Generate a UUID
Let’s assume that you are developing iOS and Android applications using Kotlin Multiplatform and you want to generate a universally unique identifier (UUID):

For this purpose, declare the expected function randomUUID() with the expect keyword in the common module. Don’t include any implementation code.
// Commonexpect fun randomUUID(): String
In each platform-specific module (iOS and Android), provide the actual implementation for the function randomUUID() expected in the common module. Use the actual keyword to mark the actual implementation.
The following examples show the implementation of this for Android and iOS. Platform-specific code uses the actual keyword and the expected name for the function.
// Androidimport java.util.*actual fun randomUUID() = UUID.randomUUID().toString()
// iOSimport platform.Foundation.NSUUIDactual fun randomUUID(): String = NSUUID().UUIDString()
Implement a logging framework
Another example of code sharing and interaction between the common and platform logic, JS and JVM in this case, in a minimalistic logging framework:
// Commonenum 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)
// JVMinternal 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.
// JSinternal actual fun writeLogMessage(message: String, logLevel: LogLevel) {when (logLevel) {LogLevel.DEBUG -> console.log(message)LogLevel.WARN -> console.warn(message)LogLevel.ERROR -> console.error(message)}}
Send and receive messages from a WebSocket
Consider developing a chat platform for iOS and Android using Kotlin Multiplatform. Let’s see how you can implement sending and receiving messages from a WebSocket.
For this purpose, define a common logic that you don’t need to duplicate in all platform modules – just add it once to the common module. However, the actual implementation of the WebSocket class differs from platform to platform. That’s why you should use expect/actual declarations for this class.
In the common module, declare the expected class PlatformSocket() with the expect keyword. Don’t include any implementation code.
//Commoninternal expect class PlatformSocket(url: String) {fun openSocket(listener: PlatformSocketListener)fun closeSocket(code: Int, reason: String)fun sendMessage(msg: String)}interface PlatformSocketListener {fun onOpen()fun onFailure(t: Throwable)fun onMessage(msg: String)fun onClosing(code: Int, reason: String)fun onClosed(code: Int, reason: String)}
In each platform-specific module (iOS and Android), provide the actual implementation for the class PlatformSocket() expected in the common module. Use the actual keyword to mark the actual implementation.
The following examples show the implementation of this for Android and iOS.
//Androidimport okhttp3.OkHttpClientimport okhttp3.Requestimport okhttp3.Responseimport okhttp3.WebSocketinternal actual class PlatformSocket actual constructor(url: String) {private val socketEndpoint = urlprivate var webSocket: WebSocket? = nullactual fun openSocket(listener: PlatformSocketListener) {val socketRequest = Request.Builder().url(socketEndpoint).build()val webClient = OkHttpClient().newBuilder().build()webSocket = webClient.newWebSocket(socketRequest,object : okhttp3.WebSocketListener() {override fun onOpen(webSocket: WebSocket, response: Response) = listener.onOpen()override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) = listener.onFailure(t)override fun onMessage(webSocket: WebSocket, text: String) = listener.onMessage(text)override fun onClosing(webSocket: WebSocket, code: Int, reason: String) = listener.onClosing(code, reason)override fun onClosed(webSocket: WebSocket, code: Int, reason: String) = listener.onClosed(code, reason)})}actual fun closeSocket(code: Int, reason: String) {webSocket?.close(code, reason)webSocket = null}actual fun sendMessage(msg: String) {webSocket?.send(msg)}}
Android implementation uses the third-party library OkHttp. Add the corresponding dependency to build.gradle(.kts) in the shared module:
【Kotlin】
sourceSets {val androidMain by getting {dependencies {implementation("com.squareup.okhttp3:okhttp:$okhttp_version")}}}
【Groovy】
commonMain {dependencies {implementation "com.squareup.okhttp3:okhttp:$okhttp_version"}}
iOS implementation uses NSURLSession from the standard Apple SDK and doesn’t require additional dependencies.
//iOSimport platform.Foundation.*import platform.darwin.NSObjectinternal actual class PlatformSocket actual constructor(url: String) {private val socketEndpoint = NSURL.URLWithString(url)!!private var webSocket: NSURLSessionWebSocketTask? = nullactual fun openSocket(listener: PlatformSocketListener) {val urlSession = NSURLSession.sessionWithConfiguration(configuration = NSURLSessionConfiguration.defaultSessionConfiguration(),delegate = object : NSObject(), NSURLSessionWebSocketDelegateProtocol {override fun URLSession(session: NSURLSession,webSocketTask: NSURLSessionWebSocketTask,didOpenWithProtocol: String?) {listener.onOpen()}override fun URLSession(session: NSURLSession,webSocketTask: NSURLSessionWebSocketTask,didCloseWithCode: NSURLSessionWebSocketCloseCode,reason: NSData?) {listener.onClosed(didCloseWithCode.toInt(), reason.toString())}},delegateQueue = NSOperationQueue.currentQueue())webSocket = urlSession.webSocketTaskWithURL(socketEndpoint)listenMessages(listener)webSocket?.resume()}private fun listenMessages(listener: PlatformSocketListener) {webSocket?.receiveMessageWithCompletionHandler { message, nsError ->when {nsError != null -> {listener.onFailure(Throwable(nsError.description))}message != null -> {message.string?.let { listener.onMessage(it) }}}listenMessages(listener)}}actual fun closeSocket(code: Int, reason: String) {webSocket?.cancelWithCloseCode(code.toLong(), null)webSocket = null}actual fun sendMessage(msg: String) {val message = NSURLSessionWebSocketMessage(msg)webSocket?.sendMessage(message) { err ->err?.let { println("send $msg error: $it") }}}}
And here is the common logic in the common module that uses the platform-specific class PlatformSocket().
//Commonclass AppSocket(url: String) {private val ws = PlatformSocket(url)var socketError: Throwable? = nullprivate setvar currentState: State = State.CLOSEDprivate set(value) {field = valuestateListener?.invoke(value)}var stateListener: ((State) -> Unit)? = nullset(value) {field = valuevalue?.invoke(currentState)}var messageListener: ((msg: String) -> Unit)? = nullfun connect() {if (currentState != State.CLOSED) {throw IllegalStateException("The socket is available.")}socketError = nullcurrentState = State.CONNECTINGws.openSocket(socketListener)}fun disconnect() {if (currentState != State.CLOSED) {currentState = State.CLOSINGws.closeSocket(1000, "The user has closed the connection.")}}fun send(msg: String) {if (currentState != State.CONNECTED) throw IllegalStateException("The connection is lost.")ws.sendMessage(msg)}private val socketListener = object : PlatformSocketListener {override fun onOpen() {currentState = State.CONNECTED}override fun onFailure(t: Throwable) {socketError = tcurrentState = State.CLOSED}override fun onMessage(msg: String) {messageListener?.invoke(msg)}override fun onClosing(code: Int, reason: String) {currentState = State.CLOSING}override fun onClosed(code: Int, reason: String) {currentState = State.CLOSED}}enum class State {CONNECTING,CONNECTED,CLOSING,CLOSED}}
Rules for expected and actual declarations
The main rules regarding expected and actual declarations are:
- An expected declaration is marked with the
expectkeyword; the actual declaration is marked with theactualkeyword. expectandactualdeclarations have the same name and are located in the same package (have the same fully qualified name).expectdeclarations never contain any implementation code and are abstract by default.In interfaces, functions in
expectdeclarations cannot have bodies, but theiractualcounterparts 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 itsactualimplementations will be required to have a body:// Commonexpect 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-specificactual 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(): Vfun set(value: V)fun getAndSet(value: V): Vfun compareAndSet(expect: V, update: V): Boolean}
actual typealias AtomicRef<V> = java.util.concurrent.atomic.AtomicReference<V>
