When “Double Work” Becomes a 2 AM Burden
2 AM, the screen is still glowing. I just finished fixing a complex promotion logic bug on Android. Just as I was about to breathe a sigh of relief, I remembered: “Damn it, there’s the iOS version too!”. I had to open Xcode, find that exact logic in Swift, and pray I didn’t misplace a single comma. Maintaining two parallel codebases for the same business logic is a true nightmare.
This situation is definitely relatable. Rewriting data processing logic across two platforms is both time-consuming and prone to data inconsistency. I once led a refactor of a 50,000+ line codebase for a fintech app. The hard-learned lesson: if your calculation logic is fragmented, you’ll eventually hit a “works on Android, fails on iOS” scenario even when the specs are identical.
What is Kotlin Multiplatform (KMP), really?
KMP isn’t like Flutter or React Native. It doesn’t try to redraw the User Interface (UI) in its own way. Instead, KMP allows you to keep the Native UI (Jetpack Compose for Android and SwiftUI for iOS). You only focus on sharing the business logic, such as the Data Layer, Domain Layer, or API calls.
Technically, KMP compiles Kotlin code into bytecode on Android and a native framework on iOS. Performance is virtually identical to pure native development. You can still access platform-specific APIs whenever needed.
The Expect/Actual Mechanism – The Master Key
Some things can’t be shared entirely, like how you store a UUID or access the Keychain. This is where the expect and actual duo shines. You declare an expect function in the common module, then implement actual separately for each platform. It’s very explicit and robust.
Building Your First Shared Module
To get started, install the Kotlin Multiplatform plugin in Android Studio. The fastest way is using the KMP Wizard to create a standard project structure.
1. Project Directory Structure
A typical KMP project is usually divided into:
- composeApp/androidMain: Where UI and logic specific to Android live.
- iosApp: Xcode project containing native SwiftUI code.
- shared/commonMain: The “heart” of the project, where shared business logic lives.
- shared/iosMain & shared/androidMain: Where platform-specific code is implemented.
2. Writing Shared Logic
Suppose you need to validate an email format. Instead of writing it in two places, you write it just once in shared/src/commonMain/kotlin/Validator.kt:
class Validator {
fun isValidEmail(email: String): Boolean {
val emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$"
return email.matches(emailRegex.toRegex())
}
}
Both Android and iOS now use a single source of truth. If your boss asks to change the regex, you only need to edit one single line. This saves at least 50% of maintenance time for small changes like this.
3. Handling Platform-Specific Parts
Want to get the OS version? In commonMain, you declare:
// In commonMain
expect fun getPlatformName(): String
In androidMain, you implement it:
actual fun getPlatformName(): String = "Android ${android.os.Build.VERSION.SDK_INT}"
And in iosMain:
import platform.UIKit.UIDevice
actual fun getPlatformName(): String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
The KMP Ecosystem: “Must-have” Tools
To code KMP effectively, you need multiplatform libraries instead of pure Java libraries. The community now provides very powerful toolsets:
- Ktor: Networking (a perfect replacement for Retrofit).
- SQLDelight: Database management (more powerful and safer than Room in a KMP environment).
- Kotlinx.serialization: High-speed JSON parsing.
- Koin: Lightweight Dependency Injection.
My experience: don’t try to migrate 100% of the logic immediately. Start with small, independent modules like Validators or Data Models. Once you’ve mastered Gradle, progress to Repositories and API calls.
Hard-learned Lesson: Don’t Forget Test Coverage
Many people mistakenly think sharing logic is enough. In reality, code running on different runtimes (JVM on Android and Native on iOS) can exhibit different behaviors. Although Kotlin/Native now has a very stable New Memory Manager, caution is still necessary.
Before refactoring, I always write thorough Unit Tests in commonTest. I only feel confident deploying when tests pass in both environments. Don’t wait until users report bugs to start panic-debugging on iOS—it’s incredibly exhausting!
Conclusion
KMP isn’t a magic wand for every project, but it’s the most balanced solution between Native performance and development speed. Instead of maintaining two teams doing the same work repeatedly, you can focus on optimizing the user experience.
If you’re planning a new mobile project, try KMP now. It will save you from pointless sleepless nights spent copy-pasting logic between platforms. Happy coding and may your app builds be smooth!

