Brifing

Migrating a Native Android App to Kotlin Multiplatform

A case study on architectural foundations for successful cross-platform migration.

ProAndroidDev - Medium2 min

1Strategic Architectural Foundations

The migration of BikeHub to Kotlin Multiplatform was not a total rewrite but a strategic evolution. By utilizing Ktor Client, Koin, and SQLDelight from the start, the app maintained portability. These libraries were chosen for their multiplatform compatibility years before the migration, significantly reducing the technical debt encountered during the transition.

2The Power of Modularization

BikeHub utilized 24 feature modules and 14 core modules, which served as the primary enabler for the migration. This structure allowed for a controlled, module-by-module transition. By isolating dependencies, the developer could ensure the Android app remained shippable at every stage, preventing the common trap of a broken, mid-migration codebase.

3Maintaining Continuous Release Cycles

A core constraint was that the Android app must remain releasable after every single change. This required a disciplined approach where every migration step had to pass tests and build successfully. This rhythm allowed the developer to pause the migration for weeks or months without leaving the project in a half-broken state.

4Refactoring Core and Feature Modules

Feature modules were converted by switching to the KMP Library plugin and moving code to commonMain. Android-specific code was treated as a code smell and pushed to the edges behind interfaces. This granular approach meant that if a specific module conversion failed, the blast radius was limited to that single isolated component.

5Minimizing Platform-Specific Code

The iOS implementation remains intentionally thin, consisting of only 304 lines of Swift code. Most platform-specific logic resides in Kotlin's iosMain source set, sitting alongside Android and web counterparts. This strategy ensures that the majority of business logic remains shared, tested, and maintained within the unified Kotlin codebase rather than platform-specific files.

6Lessons in Dependency Management

The developer noted that extracting more granular core modules, such as separating config and environment from common, simplified the mental overhead. While this resulted in a more verbose module graph, it made the dependency order binding and clear. This modular clarity was essential for managing the complexity of the multiplatform target compilation.

7Retrospective Migration Improvements

Reflecting on the process, the author suggests adding a second target to the CI pipeline immediately after the first module conversion. Relying solely on the Android build can hide platform-specific issues that only surface when compiling for iOS or WebAssembly. Early multi-target validation would have surfaced hidden problems much faster during development.

8The Reality of Portability

This case study demonstrates that successful KMP migration is largely decided by architectural decisions made long before the shift. By prioritizing portable libraries and strict modularization, the developer transformed a native Android app into a cross-platform solution. The project proves that incremental, well-structured changes are more sustainable than heroic, full-scale rewrites.

Original article: ProAndroidDev - Medium — Migrating a Native Android App to Kotlin Multiplatform