Back in 2019, we started experimenting with Kotlin Multiplatform Mobile (KMM). As a huge fan of Kotlin, I was excited by the idea of sharing code across Android and iOS without sacrificing the native experience. The promise of writing shared logic once and reusing it across platforms was incredibly tempting, so I've decided to test it out on a few smaller projects.
While KMM’s potential is clear, understanding its nuances and implementing it effectively is key. In this article, I'll share what we've discovered along the way - the capabilities, the limitations, and the lessons we learned from real-world usage.
What KMM is
Kotlin Multiplatform Mobile is a powerful tool for reusing code across platforms, but its real power lies in understanding what it’s designed to do - and what it isn’t. Here’s a closer look at the key aspects that make KMM unique:
- Cross-platform code sharing isn’t a new idea: Similar to older approaches like J2ObjC or shared C++ libraries, KMM focuses on reusing non-UI code. However, it modernizes the process with much better tooling and language support.
- Streamlined code-sharing: KMM provides a seamless way to reuse code for API integration and business logic, allowing teams to reduce duplication without sacrificing native capabilities.
- Automatic language bindings: Kotlin code is automatically exposed to Swift/Objective-C on iOS. Similarly, Objective-C/Swift APIs are easily accessible from Kotlin.
- Native user experience (UX): KMM ensures that the UI layer remains fully native, enabling developers to deliver platform-specific experiences that take full advantage of iOS and Android capabilities.
- Third-party library integration: Using third-party libraries in KMM is as straight-forward as in native development. KMM doesn’t impose additional restrictions, ensuring compatibility with existing libraries.
What KMM isn’t
Kotlin Multiplatform Mobile isn’t a one-size-fits-all solution—it’s built with specific goals in mind, and understanding what it doesn’t aim to do is just as important as knowing its strengths. Here’s a breakdown of what KMM isn’t:
- Not a replacement for React Native or Flutter: Unlike frameworks like React Native or Flutter, KMM doesn’t aim to share the UI layer or create a "write-once, run-anywhere" solution. Its focus is strictly on reusing backend logic and API integrations.
- Not a framework: KMM doesn’t enforce a particular structure for your app. Instead, it integrates into existing native workflows, complementing tools like Android Studio and Xcode.
- Not ideal for UI sharing: KMM leaves UI development entirely to native implementations, ensuring flexibility and platform-specific optimization rather than forcing UI reuse.
Addressing memory model and threading challenges
In the early days of KMM, one of the significant pain points was its memory model and threading. Developers faced issues with multithreading due to restrictions around shared mutable state between threads. This made writing highly performant and concurrent shared logic more complex.
However, with the introduction of KMM’s new garbage-collected memory model, these limitations have been addressed. Developers can now write multithreaded code without worrying about freezing objects or manually managing thread isolation. That said, the best practice is still to:
- Use coroutines for concurrency.
- Favor immutable state as much as possible to avoid potential threading issues.
This evolution in the memory model has made KMM far more developer-friendly and capable of handling complex, multithreaded operations in shared logic.
Early challenges and our first steps
When we first adopted KMM, the tooling wasn’t as mature as it is today. Updates to Gradle configurations, CocoaPods, and other dependencies often caused disruptions. While code itself didn’t require rewrites, maintaining the environment required careful adjustments.
Our initial experiments focused on creating shared libraries for APIs and business logic. This meant handling API integrations, data processing, and analytics in the shared codebase while leaving UI and presentation logic to native implementations.
What worked
Despite the early challenges, we found that certain aspects of KMM delivered significant value, aligning well with our goals for cross-platform development. Here’s what stood out during our experiments:
- Flexibility: Sharing backend components allowed us to reduce duplication while keeping native freedom for UI and platform-specific features.
- Stability: Since APIs and business logic are nearly identical across platforms, this approach felt natural and low-risk.
- Seamless integration: Shared libraries for APIs and analytics were easy to integrate into fully native apps, making them highly reusable.
What didn’t work
For smaller, UI-heavy applications, this approach had limited benefits. Many mobile apps are light on business logic, meaning there wasn’t enough shared code to justify the setup effort.
Experimenting with presentation logic
Encouraged by our initial results, we decided to push the boundaries of what we could achieve with KMM by including shared presentation logic. This step focused on managing app state and core interactions within the shared KMM layer, while still keeping the visual components native to each platform.
Here's what we've learned:
- Platform differences matter: Android and iOS often have slightly different expectations for how data should be structured or presented. Shared presentation logic needs careful design to handle these nuances.
- Experience is critical: Developers must deeply understand both platforms to create reusable code that genuinely works across Android and iOS. Without this expertise, the shared logic risks being functional on one platform but not the other.
Key takeaways from KMM
Implementing Kotlin Multiplatform Mobile (KMM) effectively requires more than just adopting the tool. Over time, we’ve refined our approach and identified best practices that maximize its value. Here’s what we’ve learned:
- Start small: Begin with backend logic and gradually expand into more complex shared components as your team gains confidence.
- Invest in expertise: KMM demands developers who are comfortable with both Android and iOS.
- Leverage the new memory model: The improved garbage-collected memory model makes multithreaded shared logic more feasible, but it’s still best to rely on coroutines and immutable state wherever possible.
- Focus on larger projects: KMM shines in projects with significant business logic or complex integrations. For smaller apps, the setup and coordination effort may outweigh the benefits.
- Reusability for native apps: Use KMM to create shared libraries for APIs, analytics, or other backend tasks that fully native apps can integrate seamlessly.
Final thoughts
Kotlin Multiplatform Mobile is a smart choice for larger, more complex projects with significant backend or business logic to share. Its ability to streamline development and reduce duplication makes it an excellent tool for cross-platform teams. However, for smaller projects, the coordination and structural overhead might outweigh the benefits.
If your team has the expertise and your project has enough shared logic to justify it, KMM can help accelerate development while maintaining the flexibility of native platforms. As the tooling continues to improve, it’s becoming an increasingly viable option for modern mobile development.