Kotlin Common Code
Introduction
Kotlin Multiplatform is a powerful technology that allows developers to share code between different platforms, including Android, iOS, web, and desktop applications. At the heart of this technology is the concept of Kotlin Common Code - the shared codebase that works across all target platforms.
In this tutorial, we'll explore how to write, organize, and maintain common code in Kotlin Multiplatform projects. You'll learn how to maximize code sharing while respecting platform-specific requirements, resulting in more efficient development and easier maintenance.
What is Kotlin Common Code?
Kotlin Common Code refers to the platform-independent code written in Kotlin that can be compiled and run on any supported platform. This code is typically placed in a special source set called commonMain
in Kotlin Multiplatform projects.
The key benefits of writing common code include:
- Reduced duplication: Write business logic, algorithms, and data models once instead of implementing them for each platform
- Consistent behavior: Ensure all platforms behave the same way for core functionality
- Easier maintenance: Bug fixes and feature updates need to be implemented only once
- Faster development: Less code to write and test
Setting Up a Multiplatform Project Structure
Let's look at a typical Kotlin Multiplatform project structure:
src/
├── commonMain/ # Common code for all platforms
│ ├── kotlin/ # Kotlin source files
│ └── resources/ # Common resources
├── androidMain/ # Android-specific code
│ ├── kotlin/
│ └── resources/
├── iosMain/ # iOS-specific code
│ ├── kotlin/
│ └── resources/
└── jvmMain/ # JVM-specific code
├── kotlin/
└── resources/
The commonMain
directory is where all your shared code will live. Each platform-specific directory contains code that will only be compiled for that particular platform.
Writing Your First Common Code
Let's start with a simple example of common code:
// In commonMain/kotlin/com/example/Person.kt
package com.example
class Person(val name: String, val age: Int) {
fun isAdult(): Boolean {
return age >= 18
}
override fun toString(): String {
return "Person(name=$name, age=$age)"
}
}
This Person
class can be used across all platforms without any modifications. Now let's write a function to greet a person:
// In commonMain/kotlin/com/example/Greeting.kt
package com.example
fun greet(person: Person): String {
return "Hello, ${person.name}! " +
if (person.isAdult()) "Welcome!" else "You must be accompanied by an adult."
}
Working with Platform-Specific Code
Sometimes you'll need functionality that varies across platforms. Kotlin Multiplatform handles this through expect/actual declarations.
Using expect/actual Declarations
In your common code, you define an expected declaration:
// In commonMain/kotlin/com/example/Platform.kt
package com.example
expect class Platform() {
val name: String
fun getVersion(): String
}
expect fun getPlatformGreeting(): String
Then you provide actual implementations in each platform-specific source set:
// In androidMain/kotlin/com/example/Platform.kt
package com.example
actual class Platform actual constructor() {
actual val name: String = "Android"
actual fun getVersion(): String = android.os.Build.VERSION.RELEASE
}
actual fun getPlatformGreeting(): String = "Hello from Android!"
// In iosMain/kotlin/com/example/Platform.kt
package com.example
import platform.UIKit.UIDevice
actual class Platform actual constructor() {
actual val name: String = "iOS"
actual fun getVersion(): String = UIDevice.currentDevice.systemVersion
}
actual fun getPlatformGreeting(): String = "Hello from iOS!"
// In jvmMain/kotlin/com/example/Platform.kt
package com.example
actual class Platform actual constructor() {
actual val name: String = "JVM"
actual fun getVersion(): String = System.getProperty("java.version")
}
actual fun getPlatformGreeting(): String = "Hello from JVM!"
Using the Platform-Specific Code in Common Code
Now you can use these platform-specific implementations in your common code:
// In commonMain/kotlin/com/example/App.kt
package com.example
fun appInfo(): String {
val platform = Platform()
return "Running on ${platform.name} ${platform.getVersion()}"
}
fun welcome(): String {
return getPlatformGreeting() + " Welcome to our Kotlin Multiplatform app!"
}
Accessing Platform Libraries from Common Code
Sometimes you need to use platform-specific libraries in your common code. Here's how to do it:
Using Interfaces
Define interfaces in common code, and implement them for each platform:
// In commonMain/kotlin/com/example/Logger.kt
package com.example
interface Logger {
fun debug(message: String)
fun info(message: String)
fun error(message: String, throwable: Throwable? = null)
}
// In commonMain/kotlin/com/example/LoggerProvider.kt
package com.example
expect object LoggerProvider {
fun createLogger(tag: String): Logger
}
Implement platform-specific versions:
// In androidMain/kotlin/com/example/LoggerProvider.kt
package com.example
import android.util.Log
actual object LoggerProvider {
actual fun createLogger(tag: String): Logger = AndroidLogger(tag)
private class AndroidLogger(private val tag: String) : Logger {
override fun debug(message: String) {
Log.d(tag, message)
}
override fun info(message: String) {
Log.i(tag, message)
}
override fun error(message: String, throwable: Throwable?) {
if (throwable != null) {
Log.e(tag, message, throwable)
} else {
Log.e(tag, message)
}
}
}
}
Real-World Example: Networking Client
Let's create a more practical example - a networking client that works across platforms:
// In commonMain/kotlin/com/example/networking/HttpClient.kt
package com.example.networking
expect class HttpClient() {
suspend fun get(url: String): String
suspend fun post(url: String, body: String): String
}
// In commonMain/kotlin/com/example/networking/UserApi.kt
package com.example.networking
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@Serializable
data class User(val id: Int, val name: String, val email: String)
class UserApi {
private val httpClient = HttpClient()
private val baseUrl = "https://api.example.com/users"
private val json = Json { ignoreUnknownKeys = true }
suspend fun getUser(id: Int): User {
val response = httpClient.get("$baseUrl/$id")
return json.decodeFromString(response)
}
suspend fun createUser(user: User): User {
val body = json.encodeToString(user)
val response = httpClient.post(baseUrl, body)
return json.decodeFromString(response)
}
}
Then implement the HttpClient
for each platform:
// In androidMain/kotlin/com/example/networking/HttpClient.kt
package com.example.networking
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.net.HttpURLConnection
import java.net.URL
actual class HttpClient actual constructor() {
actual suspend fun get(url: String): String = withContext(Dispatchers.IO) {
val connection = URL(url).openConnection() as HttpURLConnection
connection.requestMethod = "GET"
connection.inputStream.bufferedReader().use { it.readText() }
}
actual suspend fun post(url: String, body: String): String = withContext(Dispatchers.IO) {
val connection = URL(url).openConnection() as HttpURLConnection
connection.requestMethod = "POST"
connection.doOutput = true
connection.setRequestProperty("Content-Type", "application/json")
connection.outputStream.use { os ->
os.write(body.toByteArray())
os.flush()
}
connection.inputStream.bufferedReader().use { it.readText() }
}
}
Best Practices for Common Code
To make the most of Kotlin Common Code, follow these best practices:
- Maximize code sharing: Try to put as much logic as possible in the common module
- Use interfaces for platform-specific functionality: Define interfaces in common code and implement them per platform
- Leverage multiplatform libraries: Use libraries like Ktor, SQLDelight, and kotlinx.serialization that are designed for multiplatform
- Keep platform-specific code minimal: Only use platform-specific code when absolutely necessary
- Use expect/actual wisely: Don't overuse it; consider if an interface-based approach would be cleaner
- Test common code thoroughly: Write tests for your common code to ensure it works correctly across platforms
Dependencies in Common Code
You can use Kotlin Multiplatform libraries in your common code. Here's how to add dependencies in your build.gradle.kts
file:
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
implementation("io.ktor:ktor-client-core:2.3.4")
}
}
val androidMain by getting {
dependencies {
implementation("io.ktor:ktor-client-android:2.3.4")
}
}
val iosMain by getting {
dependencies {
implementation("io.ktor:ktor-client-ios:2.3.4")
}
}
}
}
Summary
Kotlin Common Code is the foundation of Kotlin Multiplatform development. By writing shared code that works across platforms, you can:
- Reduce code duplication
- Maintain consistent behavior
- Speed up development
- Simplify maintenance
We've covered the basics of writing common code, working with platform-specific requirements using expect/actual declarations, and creating interfaces to abstract platform functionality. We've also explored a real-world example of a networking client that can be used across platforms.
As you continue developing with Kotlin Multiplatform, focus on maximizing code sharing while respecting the unique requirements of each platform.
Additional Resources
- Official Kotlin Multiplatform Documentation
- Kotlin Multiplatform Mobile Developer Portal
- KMP Showcase Apps
- Kotlin Multiplatform Libraries
Exercises
- Create a simple multiplatform project with a
Person
class in common code and platform-specific greeting functions - Implement a
DateFormatter
class with expect/actual declarations to format dates according to platform conventions - Build a simple calculator app with shared business logic in common code and platform-specific UIs
- Create a file storage utility with a common interface and platform-specific implementations
- Implement a simple API client using Ktor that can be used from both Android and iOS
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)