If you’re developing mobile applications for different platforms with Kotlin Multiplatform Mobile and need to access platform-specific APIs that implement required functionality (for example, generating a UUID), you can use the Kotlin mechanism of expected and actual declarations.
With this mechanism, a common module defines an expected declaration, and platform modules must provide actual declarations that correspond to the expected one. This works for most Kotlin declarations, such as functions, classes, interfaces, enums, 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.
For simplicity, the following examples use the intuitive target names iOS and Android. However, in your Gradle build files, you need to use a specific target name from the list of supported targets.
Let's assume that you are developing iOS and Android applications using Kotlin Multiplatform Mobile 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.
//Common
expect 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.
//Android
import java.util.*
actual fun randomUUID() = UUID.randomUUID().toString()
//iOS
import platform.Foundation.NSUUID
actual fun randomUUID(): String = NSUUID().UUIDString()
Example: Send and receive messages from a WebSocket
Finally, let’s assume that you are developing a chat platform for iOS and Android using Kotlin Multiplatform Mobile. 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.
//Common
internal 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.
//Android
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.WebSocket
internal actual class PlatformSocket actual constructor(url: String) {
private val socketEndpoint = url
private var webSocket: WebSocket? = null
actual 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: