The promise of truly shared business logic across mobile platforms has long been a developer’s dream. Kotlin Multiplatform (KMP) isn’t just a dream; it’s a practical reality that lets us write a single codebase for Android and iOS. This approach significantly reduces development time and ensures consistency across user experiences. But how do you actually get started with it?
Key Takeaways
- Initialize a new Kotlin Multiplatform project using Android Studio’s built-in wizard to set up the foundational project structure.
- Configure the shared module’s build.gradle.kts file to include common dependencies and platform-specific source sets for Android and iOS.
- Implement platform-specific expect/actual declarations to handle functionalities that require native API access, such as secure storage or UI elements.
- Build and run the Android application directly from Android Studio, and integrate the shared module into an existing iOS project via CocoaPods or a similar dependency manager.
- Debug shared code by setting breakpoints within the common module and simultaneously running both Android and iOS applications to trace execution flow.
1. Set Up Your Development Environment
Before writing a single line of KMP code, ensure your environment is correctly configured. This step is critical; a misconfigured setup can lead to hours of frustrating debugging. You’ll need Android Studio, ideally the latest stable version, which currently offers excellent KMP tooling support. For iOS development, a macOS machine with Xcode installed is non-negotiable. I’ve seen teams try to skirt around this with cloud-based macOS, but for serious development and debugging, local Xcode is always superior.
Make sure your Kotlin plugin in Android Studio is up to date. Go to File > Settings > Plugins (on macOS, Android Studio > Preferences > Plugins) and check for updates to the Kotlin plugin. I always recommend enabling the experimental features for Kotlin Multiplatform mobile in the IDE settings; this often unlocks better autocompletion and error highlighting. Navigate to File > Settings > Languages & Frameworks > Kotlin > Kotlin Multiplatform Mobile and tick the box for “Enable Kotlin Multiplatform Mobile development.”
Pro Tip: Don’t forget your Java Development Kit (JDK). Android Studio usually bundles one, but sometimes older projects might point to an incompatible version. Verify you’re using JDK 17 or newer for optimal compatibility with the latest Gradle and Kotlin versions. You can check this in File > Project Structure > SDK Location.
2. Create a New Kotlin Multiplatform Project
Once your environment is ready, let’s create the project. Android Studio simplifies this significantly. Open Android Studio and select New Project. In the project template selection, search for “Kotlin Multiplatform App.” This template provides a solid starting point with the necessary module structure already in place. Trust me, trying to set this up manually from scratch is a pain; I once spent two days configuring Gradle for a custom KMP setup only to find a minor typo that broke everything. Use the template.
Click Next. You’ll be prompted for project details:
- Application Name: Choose something descriptive, like “SharedLogicApp.”
- Package Name: Standard Java/Kotlin package naming, e.g., “com.example.sharedlogicapp.”
- Save location: Where your project files will reside.
- Minimum SDK version: For Android, typically API 21 or higher.
- Project Template: Keep “Application” selected.
Click Next again. You’ll then configure the iOS framework distribution. For most projects, CocoaPods dependency manager is the most straightforward option for integrating the shared module into an iOS project. Select this. You can also choose “Regular framework” if you prefer manual integration or “Gradle wrapper for CocoaPods.” I find CocoaPods to be the least hassle for getting started.
Click Finish. Android Studio will now create and sync your project. This might take a few minutes as Gradle downloads dependencies.
Common Mistake: Ignoring Gradle sync errors. If Gradle fails to sync, don’t just close Android Studio. Read the error messages carefully in the “Build” output window. Often, it’s a network issue, a missing SDK component, or an incorrect JDK path. Address these immediately.
3. Explore the Project Structure and Shared Module
After successful synchronization, you’ll see a project structure similar to this:
androidApp/: The Android-specific application module.iosApp/: A simple Xcode project that consumes the shared module.shared/: This is the heart of your KMP project, containing the common code.
Within the shared module, you’ll find:
src/commonMain/kotlin/: Code written here is platform-agnostic and compiled for both Android and iOS. This is where your core business logic, data models, and API calls will live.src/androidMain/kotlin/: Android-specific implementations forexpectdeclarations or Android-only code.src/iosMain/kotlin/: iOS-specific implementations forexpectdeclarations or iOS-only code.
Open shared/build.gradle.kts. This file is crucial. It defines the targets (Android, iOS), dependencies, and source sets. You’ll see lines like kotlin { android() ; ios() } and sourceSets { commonMain { dependencies { ... } } }. This is where you’ll add shared libraries, like Ktor for networking or kotlinx.serialization for JSON parsing. For instance, to add Ktor client, you’d add:
commonMain.dependencies { implementation("io.ktor:ktor-client-core:2.3.8") implementation("io.ktor:ktor-client-content-negotiation:2.3.8") implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.8") } androidMain.dependencies { implementation("io.ktor:ktor-client-android:2.3.8") } iosMain.dependencies { implementation("io.ktor:ktor-client-darwin:2.3.8") }
Notice how ktor-client-core is in commonMain, while platform-specific engines (ktor-client-android and ktor-client-darwin) are in their respective platform source sets. This is the essence of KMP dependency management.
Pro Tip: Always keep your dependency versions consistent. Using different versions across common, Android, and iOS source sets can lead to subtle runtime bugs that are incredibly difficult to diagnose. I maintain a versions.properties file in the project root to centralize all dependency versions.
4. Implement Shared Logic with Expect/Actual
Sometimes, your shared code needs to interact with platform-specific APIs. This is where expect and actual declarations come into play. Let’s say you need to get a unique device ID, which is handled differently on Android and iOS. I faced this exact scenario for a client’s analytics SDK last year. We needed a consistent client ID but the underlying system calls varied.
In shared/src/commonMain/kotlin/com/example/sharedlogicapp/Platform.kt (or a similar path), you’d declare an expect class or function:
package com.example.sharedlogicapp expect class Platform() { val name: String fun getDeviceId(): String }
Then, in shared/src/androidMain/kotlin/com/example/sharedlogicapp/PlatformAndroid.kt, you provide the actual implementation for Android:
package com.example.sharedlogicapp import android.os.Build actual class Platform actual constructor() { actual val name: String = "Android ${Build.VERSION.SDK_INT}" actual fun getDeviceId(): String { // This is a simplified example; use appropriate methods for actual device IDs return "ANDROID_DEVICE_${Build.FINGERPRINT}" } }
And in shared/src/iosMain/kotlin/com/example/sharedlogicapp/PlatformiOS.kt, the actual implementation for iOS:
package com.example.sharedlogicapp import platform.UIKit.UIDevice actual class Platform actual constructor() { actual val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion() actual fun getDeviceId(): String { // In a real app, use IdentifierForVendor or other secure methods. // This is illustrative. return UIDevice.currentDevice.identifierForVendor?.UUIDString ?: "IOS_UNKNOWN_DEVICE" } }
Now, any common code can simply call Platform().getDeviceId(), and KMP automatically uses the correct platform-specific implementation at compile time. This abstraction is incredibly powerful for keeping your core logic clean.
Common Mistake: Forgetting the actual keyword. If you declare an expect and don’t provide an actual implementation for a target, the build will fail with a “Missing actual declaration” error. It’s a common oversight, especially when adding new targets or refactoring.
5. Build and Run the Android Application
Building and running the Android part of your KMP project is just like any other Android application. Select the androidApp module in the run configurations dropdown in Android Studio. Choose your target device or emulator, and click the Run button (the green play icon). The application should build and launch, displaying “Hello, Android!” or whatever initial UI the template provided.
Inside androidApp/src/main/java/.../MainActivity.kt, you’ll see how the shared module is called. Typically, there’s a line like Greeting().greet() or similar, demonstrating the shared code interaction. You can add a text view and display Platform().getDeviceId() to verify your expect/actual implementation.
// Inside MainActivity.kt class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MyApplicationTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { val platform = Platform() Text("Hello from ${platform.name}! Device ID: ${platform.getDeviceId()}") } } } } }
This direct integration makes Android development with KMP feel very native.
6. Integrate and Run the iOS Application
This step requires Xcode. Navigate to the iosApp directory in your project structure. If you chose CocoaPods, you’ll find a iosApp.xcworkspace file. Always open the .xcworkspace file, not the .xcodeproj file, when using CocoaPods. This ensures all pods, including your shared KMP module, are correctly loaded. I’ve seen developers waste hours because they opened the wrong file.
First, you might need to install CocoaPods if you haven’t already: sudo gem install cocoapods. Then, from the root of your project (where build.gradle.kts is), run ./gradlew :shared:podInstall. This command generates the necessary CocoaPods files and links your shared module. You might need to run this after significant changes to your shared module’s build.gradle.kts.
Once the workspace is open in Xcode, select a simulator (e.g., iPhone 15 Pro) and click the Run button. Xcode will build the iOS application, which includes compiling your shared Kotlin code into a framework and linking it. The app should launch and display “Hello, iOS!” (or similar). The iosApp/iOSApp.swift or iosApp/ContentView.swift will show how the shared Kotlin code is accessed from Swift:
// Inside ContentView.swift (for SwiftUI) import SwiftUI import shared // This imports your shared module struct ContentView: View { var body: some View { let platform = Platform() // Accessing the shared Platform class Text("Hello from \(platform.name)! Device ID: \(platform.getDeviceId())") } }
The shared module is exposed as an Objective-C framework, making it seamlessly callable from Swift.
Case Study: At my previous firm, we had a legacy iOS app and a new Android app. The client wanted to unify their complex pricing logic. We migrated the entire pricing calculation engine, which involved over 30 classes and 5,000 lines of code, to a KMP shared module. The initial setup took about a week, including figuring out some tricky Swift/Kotlin interoperability for custom data types. Once the module was stable, integrating it into both existing apps took only a few days each. We reduced maintenance overhead for that critical business logic by 50% and eliminated discrepancies between the two platforms. The specific tools used were kotlinx.serialization for data transfer objects and Ktor Client for API calls. The project timeline was 3 months to completion, including comprehensive testing, and resulted in a 20% faster feature delivery for new pricing models.
7. Debugging Shared Code
Debugging KMP shared code is one of its strongest features. You can set breakpoints directly in your commonMain source files within Android Studio. When you run the Android app with the debugger attached, these breakpoints will hit just like native Kotlin code. This is incredibly intuitive.
For iOS, it’s a bit different but still powerful. When you run the iOS app from Xcode, your Kotlin code runs as part of the iOS process. To debug the Kotlin code, you can attach the Android Studio debugger to the running iOS process. Go to Run > Attach to Process in Android Studio. You’ll see a list of running processes on your connected iOS device or simulator. Select the one corresponding to your iOS app. Once attached, your breakpoints in commonMain (and even iosMain) will hit, allowing you to step through the Kotlin code execution. This simultaneous debugging capability is a huge win for productivity. I always tell my junior developers to master this; it saves countless hours compared to logging everything.
Pro Tip: When debugging iOS, sometimes Android Studio struggles to attach. Ensure Xcode isn’t actively debugging the same process simultaneously. Close Xcode’s debugger, then try attaching from Android Studio. Also, make sure your shared module is built in debug mode (which is the default for development builds).
8. Advanced Topics and the Future
While this walkthrough gets you started, KMP offers much more. Consider exploring:
- State Management: Libraries like Decompose or Molecule for managing UI state in a shared way.
- Database Access: SQLDelight for shared SQL databases.
- Web Target: KMP isn’t just for mobile; you can also target JavaScript for web applications, allowing even more code reuse.
- Compose Multiplatform: This is a game-changer. It allows you to share not just business logic but also UI code across Android, iOS, desktop, and web. While still maturing for iOS UI, it’s rapidly becoming the go-to for new KMP projects aiming for maximum code sharing.
The ecosystem around Kotlin Multiplatform is evolving at an incredible pace. What was experimental three years ago is now stable and widely adopted. I firmly believe KMP, especially with Compose Multiplatform, represents the future of cross-platform development, offering unparalleled native performance and access while maximizing code reuse. It’s simply a superior approach to many alternatives that force compromises on one platform or the other.
Kotlin Multiplatform offers a compelling path to shared code for mobile applications, providing significant benefits in development efficiency and consistency. By following these steps, you can confidently embark on your KMP journey and build robust, cross-platform applications. For developers looking to stay ahead, mastering its tech trends can be a significant career boost. Furthermore, understanding the underlying principles can help debunk common developer myths surrounding cloud and multiplatform development, ensuring a smoother journey. Finally, for those interested in the broader impact of development choices, exploring how these decisions affect developer careers in an AI-driven landscape is crucial.
What is the primary benefit of using Kotlin Multiplatform over native development?
The primary benefit of Kotlin Multiplatform is the ability to share core business logic, data models, and network layers between Android and iOS applications using a single codebase, significantly reducing development time and ensuring consistent behavior across platforms. This means fewer bugs related to differing implementations.
Do I need to know Swift/Objective-C and Kotlin to use Kotlin Multiplatform?
Yes, you need to be proficient in Kotlin for the shared logic. For the platform-specific UI and any necessary native integrations, you’ll still need knowledge of Swift/Objective-C for iOS and Kotlin/Java for Android. KMP shares logic, not necessarily the entire UI, unless you use Compose Multiplatform.
Can Kotlin Multiplatform be used for web or desktop applications?
Absolutely. While often highlighted for mobile, Kotlin Multiplatform can also target JavaScript for web frontends and JVM for desktop applications. This allows for even broader code reuse beyond just Android and iOS, making it a truly versatile solution for full-stack development.
What are the common challenges when adopting Kotlin Multiplatform?
Common challenges include initial setup complexity (especially with Xcode and Gradle), managing platform-specific dependencies, understanding the expect/actual mechanism, and debugging across different IDEs (Android Studio and Xcode). The community and tooling are rapidly improving, but there’s still a learning curve.
How does Kotlin Multiplatform compare to other cross-platform frameworks like React Native or Flutter?
Kotlin Multiplatform focuses on sharing business logic while allowing native UI, offering native performance and look-and-feel. React Native and Flutter, conversely, provide their own UI rendering engines, abstracting away native UI components. KMP gives you the best of both worlds: native UI with shared logic, whereas the others share UI but might abstract away native capabilities.