Kotlin Multiplatform Mobile (KMM) Integration: Design, Implementation, and Lessons Learned
This article details Ctrip’s experience integrating Kotlin Multiplatform Mobile (KMM) into its Android and iOS ticketing apps, covering background selection, overall architecture, Android and iOS integration steps, code examples, challenges such as concurrency model differences, and ecosystem considerations.
Background and Technology Selection – Since the rise of mobile development, cross‑platform solutions have been sought to improve efficiency, reduce cost, and keep business logic consistent across platforms. While React Native and Flutter dominate the market, Ctrip chose Kotlin Multiplatform (KMM) for its native‑level performance, strong inter‑op with platform languages, and a development model close to native Android and iOS.
KMM Architecture – KMM splits code into three source sets: common (shared logic), android (JVM/Android specific), and ios (Kotlin/Native). Gradle drives the build: the common + Android source sets compile into an AAR, while the common + iOS source sets compile into a framework. This design enables reuse of data models, networking, and business logic while keeping UI code native.
Android Integration – Adding KMM to an existing Android app is straightforward: the generated AAR (or JAR for pure JVM) is published to an internal Maven repository and added as a Gradle dependency. Important practical tips include aligning the target Java version with the host app, configuring ProGuard rules to avoid NoClassDefFoundError , and handling duplicate resources correctly.
iOS Integration – iOS integration is more involved. Developers must configure cinterop to expose C/Objective‑C libraries to Kotlin, generate .def files, and adjust Gradle tasks for proper path handling. Because KMM produces a .framework that initially supports only a single architecture, a custom fat‑framework task is used to merge arm64 and x86_64 binaries. Example Gradle snippets: target.compilations["main"].cinterops.create(name) { defFile = project.file("src/nativeInterop/cinterop/xxx.def") packageName = "xxx" } The build also disables the default fat‑framework task when it conflicts with multi‑arch libraries.
Bridging Native Libraries (MMKV Example) – To share a key‑value store, an expect class MMKV is declared in the common source set, with platform‑specific actual typealias implementations: expect class MMKV actual typealias MMKV = com.tencent.mmkv.MMKV // Android actual typealias MMKV = xxx.xxx.ios.MMKV // iOS Platform‑specific wrapper functions (e.g., defaultMMKV() , set(key, value) ) are then provided, allowing the common code to use a uniform API.
Network Layer Adaptation – The existing Ctrip network stack is wrapped with Kotlin suspend functions. A Java callback API is transformed into a coroutine using suspendCancellableCoroutine , handling success, error, and cancellation uniformly across platforms.
Challenges Encountered – Kotlin/JVM and Kotlin/Native have incompatible concurrency models; Kotlin/Native requires all objects to stay on the thread they were created unless frozen or detached. This forced all coroutines to start on the main thread and use custom background‑execution helpers. Static dispatch of non‑virtual functions in Kotlin/Native caused surprising bugs when generic types were erased. Kotlin classes compile to KotlinBase on iOS, which does not inherit from NSObject , breaking class‑object interop. Singleton mutable state without @ThreadLocal leads to InvalidMutabilityException at runtime. Coroutine exception handling on the JVM sometimes fails with NoClassDefFoundError due to ServiceLoader issues. Work‑arounds include custom background execution APIs, freezing objects only when safe, and monitoring library versions for bug fixes.
Ecosystem and Tooling – The project relies on kotlinx.coroutines (multi‑thread branch), kotlinx.serialization , Ktor for HTTP, and SQLDelight for database access. Community libraries such as MMKV, MVIKotlin (experimentally), and various JetBrains and Square projects are referenced, illustrating the growing KMM ecosystem.
Conclusion – KMM enables substantial code sharing between Android and iOS while preserving native performance. Despite being in alpha, the framework proved viable for Ctrip’s ticketing business after addressing concurrency, build, and interop challenges. Ongoing monitoring of Kotlin/Native memory manager and ecosystem libraries will guide future improvements.
Ctrip Technology
Official Ctrip Technology account, sharing and discussing growth.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.