Mobile Development 33 min read

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.

Ctrip Technology
Ctrip Technology
Ctrip Technology
Kotlin Multiplatform Mobile (KMM) Integration: Design, Implementation, and Lessons Learned

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.

mobile developmentCross-PlatformiOSAndroidcoroutinesKotlin MultiplatformKMM
Ctrip Technology
Written by

Ctrip Technology

Official Ctrip Technology account, sharing and discussing growth.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.