Expected and Actual Mechanism in Kotlin Multiplatform Mobile (KMM)

Karim Reda
arconsis
Published in
5 min readJun 27, 2023

Kotlin Multiplatform Mobile (KMM) is a groundbreaking technology that empowers developers to build native applications for multiple platforms using a shared codebase. With KMM, developers can write common business logic once in Kotlin and leverage it across iOS and Android platforms, saving time and effort in the development process. One of the key features of KMM is the expected and actual mechanism, which enables platform-specific implementations while sharing code. Let’s explore this mechanism with a Bluetooth example.

Bluetooth is a widely used technology for wireless communication between devices. In the context of a mobile application, Bluetooth functionality can differ on iOS and Android due to platform-specific APIs and capabilities. However, with KMM’s expected and actual mechanism, developers can efficiently handle these platform differences while maintaining code sharing.

What is the Expected and Actual Mechanism in KMM?

The expected and actual mechanism in Kotlin Multiplatform Mobile (KMM) allows developers to define expected declarations in shared code, representing functionality that should be implemented differently on each platform. The expected declarations serve as interfaces or declarations for common functionalities. On each platform, developers provide actual implementations for these expected declarations, leveraging platform-specific APIs and functionalities. This mechanism enables code sharing while accommodating platform-specific differences, allowing developers to build native applications for multiple platforms using a shared codebase in KMM.

Advantages of Expected & Actual Mechanism in KMM

  1. Code Sharing: The primary advantage of the expect and actual mechanism is the ability to share common code across different platforms. With KMM, developers can write business logic, data models, utilities, and other shared components in Kotlin and use them seamlessly on iOS and Android platforms. This greatly reduces code duplication, increases development efficiency, and ensures consistency across platforms.
  2. Platform-specific Implementations: While code sharing is valuable, there are instances where platform-specific functionality or APIs need to be leveraged. The expect and actual mechanism allows developers to define expected declarations in the shared code, representing the desired functionality, and provide actual implementations specific to each platform. This enables developers to utilize platform-specific capabilities when needed, without compromising the benefits of code sharing.
  3. Easier Platform Migration: As technologies evolve and new platforms emerge, developers may need to adapt their applications to support different environments. The expect and actual mechanism simplifies platform migration by allowing developers to modify or extend the platform-specific implementations while keeping the shared code intact. This reduces the effort and time required to support new platforms and enables developers to reach a wider audience.
  4. Testing and Maintenance: The expect and actual mechanism enhances testing and maintenance processes. Since the shared code contains the core logic and functionality, it can be thoroughly tested in a platform-agnostic manner. This reduces the need for duplicating test cases across platforms.

Implementing Bluetooth using Expected & Actual Mechanism

To illustrate this, let’s consider a simple example of a Bluetooth-enabled application that can discover and connect to nearby devices. We will focus on the code responsible for device discovery.

Step 1: Set up your KMM project

Create a new KMM project using the instructions provided by JetBrains. Ensure you have the necessary project structure and configuration for iOS and Android platforms.

If you’re unfamiliar with the process of creating a KMM project, we encourage you to refer to our article on getting started with Kotlin Multiplatform Mobile (KMM). It provides detailed guidance on the topic.

Step 2: Create an interface in the shared module

In the shared code, we declare the BluetoothManager interface with functions representing common Bluetooth operations, such as initialization, starting and stopping device discovery, and connecting to a Bluetooth device. The expect keyword indicates that this interface needs to be implemented separately on each platform.

expect interface BluetoothManager {
fun initialize()
fun startDiscovery()
fun stopDiscovery()
fun connectToDevice(device: BluetoothDevice)
// Other Bluetooth-related functions
}

Step 3: Implement the BluetoothManager on the Android side

In the actual implementation for Android, we create a class AndroidBluetoothManager that implements the BluetoothManager interface. Inside the implementation, we utilize Android-specific Bluetooth APIs to perform the necessary operations like initialization, starting and stopping device discovery, and connecting to a Bluetooth device.

actual class AndroidBluetoothManager : BluetoothManager {
private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()

actual override fun initialize() {
// Initialize Bluetooth functionality using Android-specific APIs
// ...
}

actual override fun startDiscovery() {
// Start Bluetooth device discovery using Android-specific APIs
// ...
}

actual override fun stopDiscovery() {
// Stop Bluetooth device discovery using Android-specific APIs
// ...
}

actual override fun connectToDevice(device: BluetoothDevice) {
// Connect to the specified Bluetooth device using Android-specific APIs
// ...
}

// Implement other Bluetooth-related functions for Android
// ...
}

Step 4: Implement the BluetoothManager on the iOS side

In the actual implementation for iOS, we create a class iOSBluetoothManager that conforms to the BluetoothManager interface. We utilize iOS-specific Bluetooth APIs such as CBCentralManager to achieve Bluetooth functionality like initialization, starting and stopping device discovery, and connecting to a Bluetooth device.

actual class iOSBluetoothManager : BluetoothManager {
private val centralManager: CBCentralManager = CBCentralManager()

actual override fun initialize() {
// Initialize Bluetooth functionality using iOS-specific APIs
// ...
}

actual override fun startDiscovery() {
// Start Bluetooth device discovery using iOS-specific APIs
// ...
}

actual override fun stopDiscovery() {
// Stop Bluetooth device discovery using iOS-specific APIs
// ...
}

actual override fun connectToDevice(device: BluetoothDevice) {
// Connect to the specified Bluetooth device using iOS-specific APIs
// ...
}

// Implement other Bluetooth-related functions for iOS
// ...
}

Conclusion

The expected and actual mechanism in Kotlin Multiplatform Mobile (KMM) revolutionizes cross-platform development by enabling efficient code sharing and platform-specific customization. By leveraging this mechanism, developers can write common code once and utilize it seamlessly across iOS and Android platforms, reducing duplication and increasing development efficiency.

The flexibility offered by the expect and actual mechanism allows for tailoring functionality to meet specific platform requirements without sacrificing the benefits of code sharing. Additionally, the expect and actual mechanism simplifies testing and maintenance processes by allowing shared code to be tested independently and ensuring consistent updates across all platforms.

--

--