Back to Blog

How to Integrate Kotlin Multiplatform (KMP) into an Existing iOS App

Adopting cross-platform technologies is often associated with a complete rewrite of your existing codebase. However, Kotlin Multiplatform (KMP) is uniquely designed to avoid this. Rather than forcing you to rebuild your user interfaces, KMP allows you to share core non-UI business logic while preserving your existing, native Swift UI code.

If you have a mature, production-ready iOS app and want to start sharing databases, network models, and business validators with Android, this guide will walk you through integrating a KMP module into your existing iOS project.


The Integration Architecture

To integrate KMP into iOS, we compile the shared Kotlin code into a native iOS framework. Xcode then links this framework just like any other 3rd-party library (like a CocoaPods framework or Swift Package).

graph TD
    subgraph KMP Shared Module
        KotlinCode[Shared Kotlin Logic] -->|K/N Compiler| NativeFramework[Shared.framework]
    end
    subgraph iOS Native Project
        NativeFramework -->|Link Binary| Xcode[Xcode Build System]
        SwiftCode[Native Swift / SwiftUI] -->|Import shared| Xcode
    end

Step 1: Define the Shared Gradle Configuration

In your Kotlin Multiplatform project, the shared/build.gradle.kts configuration must declare the native targets for iOS. We use the multiplatform plugin to produce a dynamic or static framework:

plugins {
    alias(libs.plugins.kotlinMultiplatform)
    alias(libs.plugins.androidLibrary)
}

kotlin {
    androidTarget {
        compilerOptions {
            jvmTarget.set(JvmTarget.JVM_17)
        }
    }
    
    // Support modern iOS targets (Simulator and Devices)
    listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64()
    ).forEach { iosTarget ->
        iosTarget.binaries.framework {
            baseName = "shared"
            isStatic = true // Static framework for optimized size and launch speed
        }
    }

    sourceSets {
        commonMain.dependencies {
            // Shared dependencies (Ktor, SQLDelight, etc.)
        }
    }
}

Step 2: Choosing the Distribution Method

There are two primary methods to connect the generated framework to Xcode:

Method A: Direct Xcode Integration (Local Development)

For local development, you can configure Xcode to invoke Gradle directly during the build. This ensures that any changes you make in Kotlin are compiled and instantly reflected in Swift.

Add a Run Script Phase in Xcode under Build Phases:

cd "$SRCROOT/.."
./gradlew :shared:embedAndSignAppleFrameworkForXcode

Note: Ensure this run script phase is placed before the "Compile Sources" phase.

Method B: Swift Package Manager (SPM) (CI/CD Production)

For teams where iOS developers do not want to install Android Studio or Gradle locally, you can publish the KMP framework as a Swift Package. We use the KMMBridge plugin or standard Gradle tasks to compile the binaries into a .zip xcframework, generate a Package.swift manifest, and push it to a private git repository.


Step 3: Calling Kotlin from Swift

Once linked, importing and consuming your Kotlin logic in SwiftUI is seamless:

import SwiftUI
import shared // Import the compiled Kotlin Multiplatform framework

class IOSUserViewModel: ObservableObject {
    @Published var userName: String = ""
    
    private let repository = UserRepository(
        driverFactory: DatabaseDriverFactory(),
        ioDispatcher: Dispatchers.shared.io
    )
    
    func loadUser(id: Int64) {
        // Suspending functions translate into Swift async/await
        Task {
            do {
                if let user = try await repository.fetchUser(id: id) {
                    await MainActor.run {
                        self.userName = user.name
                    }
                }
            } catch {
                print("Failed to fetch user: \(error)")
            }
        }
    }
}

Key Interoperability Best Practices

When calling shared code from Swift, keep these tips in mind:

  1. Suspending Functions: Kotlin suspend functions are mapped to Swift async/await in Swift 5.5+.
  2. Kotlin Flows: Kotlin Flow does not map directly to Swift's AsyncSequence out of the box. Use compiler plugins like SKIE to automatically generate Swift-friendly reactive code.
  3. Data Classes: Kotlin data class elements translate into Swift classes. Properties are accessible, but you lose Kotlin-specific copy constructors in Swift.

Conclusion

Integrating Kotlin Multiplatform into an existing iOS app is a low-risk, incremental way to reduce code duplication. You don't have to rewrite your UI, and you can start sharing small modules (like analytical algorithms or local database wrappers) before expanding to full networking.

To see how to set up the networking and database components that you'll export to iOS, read my detailed guides on Ktor Networking in KMP and Offline Persistence with SQLDelight.


Need to Migrate Your App to Kotlin Multiplatform?

Are you looking to integrate KMP into an existing iOS or Android codebase? I specialize in modular architecture migrations, helping teams set up private SPM distributions and solve Swift-Kotlin interop bottlenecks. I provide remote contracting for engineering teams in Europe, the UK, and Canada, aligning with CEST, GMT, and EST timezones.

Let's Connect & Discuss Your Project