One Kotlin Multiplatform core for iOS and Android
A shared Kotlin core holding business logic, models and data handling, consumed natively by both an iOS and an Android application.
Transcarent / 98point6 · 2022–2024
- 2 implementations → 1shared features written once instead of twice
- 2platforms on one business-logic core
- 0reimplementations of shared rules
Context
Two native healthcare applications shared one set of business rules. Every shared feature was specified once and implemented twice, and the tax was not just duplicated effort but behavioural drift: two implementations of the same rule diverge quietly, and in healthcare the divergence is the bug.
Why the obvious approach failed
There are two conventional answers and both cost more than they look. Continuing to build twice is the status quo, and its real price is not the duplicated hours but the drift, which surfaces as defects that reproduce on one platform only. Rewriting both clients in a single cross-platform UI framework removes the duplication by removing the native layer — which for a healthcare product means giving up native accessibility and platform interaction conventions to solve a problem that lives entirely in the business logic. Kotlin Multiplatform is the middle path: share the layer that has no business being different, keep the layer that does.
The mechanism
The shared library holds models, business rules and data handling, written once in Kotlin and consumed natively by both platforms. Each application keeps its own native UI, so the parts of the product a user actually touches remain fully platform-idiomatic.
Sharing logic only works if the shared layer is trustworthy, so the architectural work was accompanied by a raised floor: CI-level linting and static analysis gates in front of every merge, so the bar rose with the architecture rather than after it.
Renovate was rolled out company-wide, making dependency freshness an automated process rather than a periodic chore — which reduced both security exposure and the class of bug that comes from long-deferred upgrades.
Alongside the KMP work I introduced Jetpack Compose to the Android codebase, and built the cross-platform onboarding flow spanning Flutter on mobile and React on web, maintaining the Node.js backend behind it.
The hard part
[NICK TO CONFIRM] Candidate: adoption on the iOS side. A shared Kotlin core is an Android-team artifact arriving in an iOS build, and the engineering problem — toolchain, build times, debugging across the boundary — is inseparable from the social one, which is that the iOS team has to accept a dependency they did not choose in a language they do not write. The library succeeds or fails on whether that experience is good enough that they stop noticing it.
Results
Effort on shared features fell roughly in half, and drift on shared logic stopped being possible by construction rather than by discipline. The caveat that matters: 'roughly half' is a team-level estimate against the previous build-twice baseline, not an instrumented measurement, and it excludes the up-front cost of standing the library up. The structural claim is the stronger one — a rule implemented once cannot diverge from itself.
What I'd do differently
[NICK TO CONFIRM] Candidate: start with a narrower shared surface. The instinct is to share everything shareable; the better sequencing is to make one feature genuinely excellent across the boundary first, so the second is argued for by evidence rather than by architecture diagrams.