Learning Kotlin Multiplatform

Kotlin Multiplatform: Expect/Actual and Default Implementations — A Clarification

The right way to use interfaces for shared logic and platform-specific behavior

Cazimir Roman
3 min readNov 7, 2024

Kotlin Multiplatform (KMP) empowers developers to share code between platforms like Android, iOS, desktop and the web. A core concept in KMP is the expect/actual mechanism. However, there’s a common misunderstanding about how default implementations work with expect classes, which can lead to confusion and errors. This article clears up the confusion and provides the correct way to use default implementations in your KMP projects.

The Problem: Expect Classes and Default Implementations

Let’s consider a scenario where we want to define a DataProvider class with a common function loadData() that has a default implementation, along with a platform-specific function platformSpecificLoad():

// Incorrect Approach: Expect class with default implementation
expect class DataProvider {
fun loadData(): String = "Default Data" // Default implementation
expect fun platformSpecificLoad(): String
}


actual class DataProvider : DataProvider { // Android implementation
actual override fun platformSpecificLoad(): String = "Android Data"
}

In this (incorrect) approach, you might expect the Android implementation to ONLY inherit the default loadData() implementation. However, this is not how expect/actual works. The expect keyword on the class requires each platform to provide a complete implementation of the class, even if a default implementation exists in common code. The Android DataProvider replaces the common implementation, it doesn’t extend it.

The Solution: Interfaces with Default Implementations

The correct way to achieve default implementations with platform-specific variations is to use an interface:

// Correct Approach: Interface with default implementation

// Common Code
expect class DataProvider() : IDataProvider // Note: empty constructor

interface IDataProvider {
fun commonProcessing(data: String): String = "Processed: $data"
fun platformSpecificLoad(): String
fun loadData(): String = commonProcessing(platformSpecificLoad())
}


// Android Implementation
actual class DataProvider : IDataProvider {
actual override fun platformSpecificLoad(): String = "Android Data"
}

// iOS Implementation (Similar)
actual class DataProvider : IDataProvider {
actual override fun platformSpecificLoad(): String = "iOS Data"
}

With this approach, the IDataProvider interface defines the contract and the default implementations. The expect class DataProvider simply signals that platform-specific classes implementing this interface are required. The actual classes then implement the interface, inheriting the default implementations. This is standard Kotlin behavior and works seamlessly with KMP.

Why This Works

The key difference is that the interface owns the default implementation. The actual class then implements the interface, so it automatically inherits the default implementations. The expect class in this correct approach essentially acts as a bridge, ensuring each platform provides a class that conforms to the interface.

Example Usage

Now you can use the DataProvider in your shared code, knowing it will have the correct platform-specific implementation:

// Common Code
fun getData(): String {
val provider = DataProvider()
return provider.loadData() // Calls the correct platform-specific implementation
}

Conclusion

Using interfaces with default implementations is the correct and idiomatic way to handle shared logic with platform-specific variations in Kotlin Multiplatform. This approach keeps your code clean, maintainable, and avoids common pitfalls associated with trying to use default implementations directly in expect classes. By understanding this crucial difference, you can effectively leverage the power of KMP for your cross-platform development needs.

Did you find this article helpful? Let me know on Medium by giving it a clap! 👏 The more claps, the more I know to create content you love.

If you have any queries related to AI or Android, I’m always happy to help you. You can reach me on LinkedIn.

Happy Learning🚀 Happy Coding📱

--

--

Cazimir Roman
Cazimir Roman

Written by Cazimir Roman

A curious developer with a passion for learning and creating innovative solutions to complex problems.

No responses yet