WhollySoftware
Back to blogMobile

Cross-Platform Code Sharing Without Sacrificing Native Feel

Wholly Software TeamMay 7, 20257 min read
Cross-Platform Code Sharing Without Sacrificing Native Feel

When a fintech client asked for 'one codebase' for iOS and Android, what they actually needed was consistent business logic and platform-native UI, not a single UI layer. We used Kotlin Multiplatform to share the networking layer, data models, validation rules, and the transaction-calculation logic, while building the UI natively in SwiftUI and Compose. That split ended up being the most important architectural decision on the project.

The logic we shared was substantial — roughly 55% of the total codebase by line count — but it was the right 55%: API clients, serialization, caching rules, and business validation that had to behave identically on both platforms for compliance reasons. A discrepancy in how interest calculations rounded between an iOS and Android version was a real bug we'd seen on a previous non-KMP project, and sharing that logic in Kotlin eliminated the entire bug category.

We deliberately did not try to share UI code through Compose Multiplatform on this project, even though it's matured a lot. The client's brand required iOS-specific interaction patterns — swipe gestures, sheet presentations, haptics — that felt native on iOS and would have required either compromising on Compose Multiplatform's iOS rendering or building platform-specific overrides that undercut the point of sharing UI in the first place. For a different client with a simpler, more utilitarian UI and a tighter budget, we have used Compose Multiplatform for shared UI successfully — the decision is genuinely case by case.

The friction point that cost us the most time was Swift/Kotlin interop for more complex types — sealed classes and coroutines don't map cleanly to Swift's type system, and we ended up writing a thin Swift-friendly wrapper layer around the KMP module's public API specifically to keep the Swift-side code idiomatic rather than exposing Kotlin's Flow types directly, which read awkwardly from Swift.

Build tooling was rougher than advertised at the start of the project — Xcode build times for the KMP framework added close to 90 seconds to a clean build, which we later cut to about 25 seconds by properly configuring incremental compilation and avoiding unnecessary framework rebuilds in CI. Teams evaluating KMP should budget real time for this kind of build tooling investment rather than assuming it works out of the box.

Kotlin MultiplatformiOSAndroidMobile Architecture
Cross-Platform Code Sharing Without Sacrificing Native Feel — Wholly Software