Exploring Kotlin Multiplatform: Code Sharing Across Android, iOS, and Web

In modern software development, code reuse and platform consistency are key to efficiency and reducing maintenance costs. Kotlin Multiplatform (KMP) is a powerful solution that enables developers to share code across multiple platforms, including Android, iOS, web, and more. This post will guide you through understanding and implementing Kotlin Multiplatform, with detailed explanations and practical examples.

What is Kotlin Multiplatform (KMP)?

Kotlin Multiplatform (KMP) is a Kotlin feature that allows developers to write code that can be compiled to multiple platforms. Unlike other cross-platform solutions that often rely on a shared UI layer, KMP focuses on sharing business logic, data models, and other non-UI code. This results in native performance and platform-specific UIs, ensuring a high-quality user experience.

Why Use Kotlin Multiplatform?

  • Code Reuse: Write code once and use it across multiple platforms, reducing development time and effort.
  • Platform-Specific UIs: Create native user interfaces for each platform, ensuring optimal performance and user experience.
  • Reduced Maintenance: Simplify maintenance by updating shared code in one place.
  • Cost-Effective: Lower development costs by avoiding redundant code.
  • Consistency: Ensure consistent behavior across different platforms by sharing core business logic.

Setting Up a Kotlin Multiplatform Project

Let’s walk through setting up a Kotlin Multiplatform project using IntelliJ IDEA:

Step 1: Install the Kotlin Multiplatform Plugin

First, ensure that you have the Kotlin Multiplatform plugin installed in IntelliJ IDEA.

  • Go to File > Settings > Plugins.
  • Search for “Kotlin Multiplatform” and install the plugin.

Step 2: Create a New Project

  • Open IntelliJ IDEA and select Create New Project.
  • Choose “Kotlin” in the left pane, and then select “Multiplatform App” in the right pane.
  • Name your project (e.g., “MyKMPApp”) and choose a location.

Step 3: Configure Project Targets

IntelliJ IDEA will guide you through selecting the platforms you want to target. By default, it usually includes:

  • Android
  • iOS
  • JVM (for desktop applications or server-side logic)

You can customize these based on your needs.

Step 4: Project Structure

The project structure includes several modules:

  • commonMain: Contains the shared Kotlin code.
  • androidMain: Contains Android-specific code.
  • iosMain: Contains iOS-specific code.
  • jvmMain: Contains JVM-specific code.

Writing Shared Code in commonMain

The commonMain module is where you write the code that will be shared across all platforms.

Example: Simple Greeting Function

Let’s create a simple function that generates a greeting:


// In commonMain/kotlin/Greeting.kt
package com.example.mykmpapp

class Greeting {
    fun greet(): String {
        return "Hello, Kotlin Multiplatform!"
    }
}

Using Shared Code in Platform-Specific Modules

Now, let’s use the Greeting class in our Android and iOS modules.

Android Implementation

In your Android module (androidMain), create an activity and use the Greeting class.


// In androidMain/kotlin/MainActivity.kt
package com.example.mykmpapp

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val greetingTextView: TextView = findViewById(R.id.greetingTextView)
        val greeting = Greeting().greet()
        greetingTextView.text = greeting
    }
}

Update your activity_main.xml:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center">

    <TextView
        android:id="@+id/greetingTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"/>

</LinearLayout>

iOS Implementation

For iOS, you’ll need to create a Swift view controller and use the Kotlin code. This involves using the generated Objective-C headers from the Kotlin code.


// In iOSApp/ViewController.swift
import UIKit
import shared

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let greeting = Greeting().greet()
        let label = UILabel()
        label.text = greeting
        label.frame = CGRect(x: 0, y: 0, width: 300, height: 50)
        label.center = view.center
        label.textAlignment = .center
        
        view.addSubview(label)
    }
}

Handling Platform-Specific Implementations

Sometimes, you’ll need platform-specific implementations for certain functionalities. Kotlin provides the expect and actual keywords to handle this.

Example: Platform Name

First, define an expect declaration in the commonMain module:


// In commonMain/kotlin/Platform.kt
package com.example.mykmpapp

expect class Platform {
    val name: String
}

Then, provide actual implementations in the platform-specific modules:


// In androidMain/kotlin/Platform.kt
package com.example.mykmpapp

import android.os.Build

actual class Platform {
    actual val name: String = "Android ${Build.VERSION.SDK_INT}"
}

// In iosMain/kotlin/Platform.kt
package com.example.mykmpapp

import platform.UIKit.UIDevice

actual class Platform {
    actual val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}

Use this in your shared Greeting class:


// In commonMain/kotlin/Greeting.kt
package com.example.mykmpapp

class Greeting {
    fun greet(): String {
        return "Hello, Kotlin Multiplatform on ${Platform().name}!"
    }
}

Sharing Business Logic and Data Models

KMP excels at sharing business logic and data models. For instance, you can create data classes that represent your application’s data and share them across platforms.

Example: Data Class


// In commonMain/kotlin/Data.kt
package com.example.mykmpapp

data class DataModel(val id: Int, val name: String)

This data class can be used in both Android and iOS modules.

Using Libraries in KMP

Many Kotlin libraries are also multiplatform, making it easy to use them in your shared code. Some popular multiplatform libraries include:

  • Kotlinx.serialization: For serializing and deserializing data.
  • Kotlinx.coroutines: For asynchronous programming.
  • Ktor: For building HTTP clients and servers.

Example: Using Kotlinx.serialization

Add the serialization dependency to your commonMain build.gradle.kts file:


kotlin {
    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.0")
            }
        }
    }
}

Mark your data class as serializable:


// In commonMain/kotlin/Data.kt
package com.example.mykmpapp

import kotlinx.serialization.Serializable

@Serializable
data class DataModel(val id: Int, val name: String)

Advanced Topics

  • Dependency Injection: Use Koin or other DI frameworks to manage dependencies in your shared code.
  • Testing: Write unit tests for your shared code using Kotlin test frameworks.
  • UI Layer: While KMP doesn’t directly share UI, consider using frameworks like Compose Multiplatform to share UI logic.

Conclusion

Kotlin Multiplatform is a robust solution for sharing code across multiple platforms. By focusing on business logic and data models, you can create native applications with consistent behavior and reduced maintenance costs. With the practical examples and guidelines provided in this post, you are well-equipped to start exploring and implementing Kotlin Multiplatform in your projects. Embrace KMP to unlock the potential of code reuse and platform consistency in your development workflow.