Convert Your Native Project to Kotlin Multiplatform: Why, When and How
Thinking about moving to Kotlin Multiplatform? This guide will give you all the information you need to make a decision!
Table of contents
Technologies such as Flutter and React Native are often preferred by new businesses or those seeking to expand their product portfolios. The advantages of having a single codebase, a unified development team, reduced costs, and simplified maintenance are highly appealing.
However, transitioning to one of these technologies may not be an obvious decision if your company has well-established mobile products that have been implemented natively. This situation typically entails a complete rewrite of the application using a selected framework.
Kotlin Multiplatform (KMP) offers a completely new approach to cross-platform development with the main goal of improving native applications rather than replacing them. It seamlessly integrates with existing projects, enabling us to retain the benefits of native development while also enjoying the advantages of sharing code between Android and iOS.
If you don’t want to rewrite your applications but are still looking for a solution to make development more efficient, Kotlin Multiplatform stands out as a strong candidate. Let me guide you on when to choose this technology, its advantages, and a universal strategy for a seamless transition, all while keeping your digital product robust.
What is Kotlin Multiplatform?
Not everyone knows this, but the Kotlin language that we use every day for building Android applications was not designed as an Android-specific solution. It was conceived a modern language that could be compiled into other languages, enabling its use across various platforms. Similarly, Kotlin Multiplatform is just a set of official tools that streamline the process of utilizing Kotlin simultaneously on different platforms.
The cross-platform approach it adopts is refreshingly straightforward and distinct from its competitors. Instead of requiring developers to construct the entire application within a separate framework, KMP enables the sharing of existing Kotlin code across different native applications.
As a result, numerous developers and companies worldwide have embraced this technology, leveraging it to construct their digital products. Among these adopters are prominent entities such as Netflix, McDonald’s, Forbes, and 9GAG.
This article will delve into how Kotlin Multiplatform can enhance your existing native applications without necessitating a complete rewrite. However, for a comprehensive understanding of how it works and how it combines the strengths of both native and cross-platform approaches, feel free to explore my detailed article on the topic: Kotlin Multiplatform: A Smart Choice for Cross-platform Development?
Why is it worth thinking about migrating to KMP?
The decision to move your existing mobile product to a cross-platform framework used to be very difficult. Building a new Flutter or React Native application can take months or even years for larger projects. And since you are rewriting your product in a completely new technology, you cannot be sure that it will handle all the application’s features and offer the same quality and experience to your users. However, Kotlin Multiplatform changes everything in this regard. It offers a completely new approach to cross-platform development which seamlessly integrates with existing mobile applications.
Migrate gradually instead of rewriting the application
With KMP, you don’t need months of work to see the first benefits of cross-platform development. You also don’t need a new team to build a completely new product from scratch. The migration process is not about rewriting the entire app but instead about gradually moving more and more Kotlin code from the Android application to the Multiplatform module. This shared code can then be integrated into the iOS application, replacing the original Swift implementation. Simple as that.
Start using KMP at any stage of your project
Kotlin Multiplatform can be integrated into a native project at any stage of its development. It doesn’t matter whether you are planning to start a new project, have already started working on an MVP, or have a mature product that is already in production. This is a fundamental difference compared to other cross-platform solutions. When choosing between a native application and a Flutter or React Native application, for example, you’re always in an either/or situation. You can either have a 100% native app or a 100% cross-platform app. With KMP, you have complete flexibility in how much code you share between applications, so it’s always a good time to start using it.
Don’t sacrifice any native functionality
Native mobile applications typically rely on system APIs for functionalities, such as notifications, location services, camera, Bluetooth, NFC, and more. Additionally, they often utilize external third-party SDKs for tasks like analytics, image recognition, document scanning, and other complex operations.
When migrating to KMP, you don’t need to replace these native dependencies. Kotlin’s interoperability extends bidirectionally with other languages and platforms. While native applications can incorporate Kotlin, the latter can directly access native platforms without requiring any bridges.
This grants you and your team significant flexibility in implementing native features within your applications. There’s no need to search for open-source alternatives or wrappers to use system APIs or native SDKs, which is a common requirement in Flutter and React Native ecosystems.
Keep the best user experience
Despite the similarities between Android and iOS, each operating system provides a distinct user experience. Differences in animation style, scrolling behavior, navigation patterns, gestures, and user interface component behaviors are evident. Users on each platform are accustomed to the specific aesthetics of their applications, often finding it disconcerting to use the same app on a different system.
This is where Kotlin Multiplatform offers a unique native UI, shared logic approach. Application logic, which typically constitutes around 70-80% of the total codebase, can be implemented once in Kotlin. Meanwhile, the user interface still be implemented natively and remain separate for each platform, ensuring a 100% native user experience without compromise.
Keep the best performance
Native applications operate as closely to the underlying operating system as possible, devoid of any additional layers that could impact performance. The compiled code of these applications is executed directly by the operating system.
In contrast, frameworks like Flutter or React Native function differently. While their creators promise native-like performance, they differ from pure native applications. They both come with their own engines written in C++. When an app starts, the operating system only initiates the engine provided by the framework. Subsequently, the application code is executed by this engine, rather than directly by the system.
Many companies with native applications are hesitant to adopt Flutter or React Native due to potential performance implications on the final product. However, KMP diverges significantly from these frameworks in this regard. As it is not a cross-platform framework but merely a cross-platform language, it lacks its own execution environment, unlike Flutter and React’s engines.
Instead, Kotlin is always compiled into fully native binaries. This compiled code is integrated into the application and accessible from its codebase. Consequently, the final product published in the store contains no Kotlin code or additional execution layers like engines or virtual machines. Everything is executed directly by the operating system, akin to any code in a regular native application.
Deliver new features faster and fix bugs more easily
By sharing a significant portion of the codebase between applications, both development and maintenance costs can be notably reduced. For instance, imagine you’re planning a new feature for your application. If both native teams estimate it at 10 Mad Days each, the total estimate amounts to 20 Man Days. However, adopting KMP can potentially reduce this estimate to 12-13 Man Days, enabling you to deliver the feature 30-40% faster.
With KMP, the logic is implemented once in Kotlin, and the native applications only need to add the user interface atop that shared layer. Furthermore, when the majority of the codebase is shared across applications, maintenance becomes considerably simpler. If a bug arises, it only requires fixing in one place. Similarly, modifying existing logic is a one-time task.
Read also: How Much Does it Cost to Develop an App in 2024? New Cost Breakdown
Improve cooperation between native teams
Migrating a native project to KMP can have a profoundly positive impact on the teams involved. When both Android and iOS applications operate independently, the teams working on them may become increasingly isolated over time.
They may operate at different paces, interpret business requirements differently, and implement them in varying ways. Such disparities can lead to business issues when one application outpaces the other or behaves differently. Introducing shared Kotlin implementation to the project can help mitigate such scenarios and foster better collaboration between developers.
Over time, you may observe a notable exchange of knowledge between the teams. The iOS developers may initially acquaint themselves with Kotlin to understand the shared codebase, and eventually, they might even start implementing Kotlin logic independently. Conversely, the Android developers may delve into the iOS platform, particularly SwiftUI, which bears similarities to Compose.
How do I migrate to Kotlin Multiplatform?
Now you know the benefits of KMP migration, you might be wondering how the process looks in practice. The migration, when properly planned and executed, seamlessly integrates into your usual workflow. Your team can continue delivering new features, addressing existing bugs, and releasing updates to stores without disruption. At a high level, the migration process unfolds in three straightforward steps:
- Setup a Multiplatform module and connect it to both native applications.
- Move selected code from the existing Android project to the Multiplatform module.
- Integrate this code in the iOS project, replacing the original Swift implementation.
It’s important to note that this migration doesn’t entail moving all the code at once; instead, it’s an iterative process. In each iteration, we select some part of the codebase, transfer it to the KMP module, integrate it into the iOS project, and remove the original Swift implementation. Updated versions of the app are then released to the stores. Concurrently, the regular development of the application can proceed without interruption.
Start from the model
We typically start the migration process from the simplest and most self-contained part of the application: the model. This layer contains straightforward objects that represent essential business entities such as a User, Product, or Article, along with associated business rules defining their behavior. By migrating the model, our native applications can adopt a unified representation of the data they utilize.
Then migrate data sources
After migrating the model, the next step involves addressing the outermost components of the codebase, namely data sources. These encompass various types, with the most common being backend services and local databases. By sharing these components across applications, we establish a unified definition of endpoints, database tables, queries, and Data Transfer Objects (DTOs).
Add common interfaces for native APIs and SDKs
In addition to data sources, mobile applications frequently interact with system APIs and external SDKs. While these typically feature platform-specific implementations, our KMP module can furnish common interfaces that are shareable across applications. It’s important to note that the implementation of these interfaces remains entirely native. You don’t need any wrappers or bridges.
Time for a business logic
After migrating the model, data sources, system APIs, and external SDKs, the next phase targets the business logic, which integrates all the preceding components. It knows what should be done when you submit your order or which data has to be loaded after you login to the app. Having a single implementation of this logic helps mitigate critical bugs and ensures consistent behavior across mobile platforms.
The cherry on top: a shared presentation layer
Finally, we come to the decision of migrating the presentation layer, which forms the last layer before the user interface. Primarily, it manages the state of screens or user interface components, formats data, validates user inputs, and handles user interactions. By sharing this implementation, we simplify our user interface, enabling us to concentrate on delivering the best user experience. All underlying logic is implemented and tested once in the Multiplatform module.
Split migration feature-by-feature
In larger projects, it is common practice to apply feature modules. Developers split the codebase into distinct features, each representing a cohesive flow within the application. These features, such as registration, onboarding, orders, and settings, typically encompass their own presentation layer, business logic, model, data sources, and sometimes even feature-specific APIs or SDKs.
For such cases, we can enhance the process by introducing a feature-based approach, complementing the layer-by-layer migration. This supports the migration’s parallelization across different developers or teams. Each of them can focus on migrating a specific feature at their own pace, without disrupting the progress of others.
What are the risks of KMP migration and how can you mitigate them?
Kotlin Multiplatform does an excellent job of minimizing technical risk. It avoids introducing a new programming language, enables a gradual migration process, maintains 100% performance and UI for the best user experience, and retains access to all native APIs and SDKs.
Overall, KMP presents a lower adoption risk compared to Flutter or React Native, but as with any technology, it isn’t entirely risk-free. Luckily, with proper awareness and several tips which I would like to share with you, these risks can be effectively managed and avoided.
Potential imbalance in task allocation within teams
As mentioned earlier, approximately 80% of the codebase can be consolidated into Kotlin and shared across platforms. For the Android team, this transition entails placing existing implementation into a Multiplatform module without significant alterations. However, the scenario differs for the iOS team. With KMP, much of the code typically written in Swift is now implemented in Kotlin by the Android developers. If both teams are of equal size, the iOS team may find themselves with less work, as the bulk of it is handled by their Android colleagues.
One effective solution is to facilitate a gradual exchange of knowledge between teams. This approach enables the iOS developers to not only utilize the shared Kotlin implementation, but also to contribute to its creation. At Droids On Roids (top Kotlin Multiplatform app development company), we successfully implemented this strategy, fostering the interest of our iOS engineers in learning Kotlin.
Alternatively, if knowledge sharing proves challenging, consider creating asymmetric teams with more Android developers and fewer iOS developers. This adjustment ensures seamless collaboration without downtime, maintaining efficiency throughout the migration process.
Risk of ignoring iOS needs
When utilizing KMP, our shared code is predominantly written in Kotlin, and our Android projects also leverage Kotlin extensively. Consequently, the distinction between Android-specific and Multiplatform implementation can blur, as they often appear and function identically. However, it’s imperative that this shared Kotlin implementation remains easily accessible and usable on the iOS side.
Avoid situations where common code is developed without consulting iOS developers. Effective communication is paramount for successful KMP integration. The responsibility for common implementation must be shared between both native teams. Android developers should familiarize themselves with how Kotlin translates to Swift. They can achieve this by acquainting themselves with the official Kotlin-Swift interopedia, providing detailed insights into the translation process.
Conversely, iOS engineers should understand the shared code’s public API and actively participate in shaping its technical decisions. In the long term, fostering an environment where iOS developers contribute to and maintain the common Kotlin codebase not only enhances their understanding but also promotes collaboration and shared ownership.
Potential limitations in Kotlin features on iOS
JetBrains began developing Kotlin long before Swift became widely adopted in the Apple developer community. To accommodate this, they designed the Kotlin/Native compiler to translate Kotlin into Objective-C – the original, primary language for all Apple operating systems. This decision enables support for a broad range of projects, but also carries certain limitations. Basic functionalities like classes, properties, and functions translate smoothly, but more advanced features such as sealed classes, suspend functions, or flow pose challenges.
Fortunately, the SKIE compiler plugin addresses these limitations by generating Swift code instead of Objective-C. It enables support for advanced Kotlin features by translating them into Swift counterparts, such as enums with associated values, async/await functions and AsyncSequence.
Excitingly, direct Kotlin-Swift interoperability is on the horizon for 2024 as part of Kotlin Multiplatform’s roadmap. JetBrains collaborates closely with the creators of SKIE, with plans to integrate similar functionalities directly into the Kotlin/Native compiler.
Possible need for additional refactoring
The complexity of migrating to KMP can vary depending on the quality and cleanliness of the application architecture. A smooth transition is typically experienced when the user interface is properly separated from the application logic, and external dependencies are abstracted away. However, if certain areas of your architecture are less than optimal, additional changes may be necessary. While refactoring itself isn’t a hindrance, it does demand extra effort to complete the full KMP migration and reap its maximum benefits.
To tackle it efficiently, it’s best to avoid unexpected overhauls. Begin by analyzing the project to identify areas in need of changes. Then, break down this process into manageable chunks and plan each step accordingly. While refactoring takes place, sections of the codebase that don’t require adjustments can still be migrated to KMP concurrently. Strategic planning, combined with the segmentation of tasks, will empower you to maintain control over the entire migration process.
Keep in mind that KMP offers autonomy and flexibility in determining which parts of the codebase should be shared between applications. Sometimes, the effort required to refactor certain code may outweigh the benefits of sharing it. In such cases, prioritize sharing everything else and retain these specific components as separate implementations for each application.
Migration to KMP – my final thoughts
Just like a few years ago, when Google started recommending using Kotlin for native Android development, they now recommend writing application logic using Kotlin Multiplatform and sharing it with the iOS application. Nowadays, Kotlin is a standard in the industry, and we can definitely expect the same with Kotlin Multiplatform very soon.
Big companies with top-quality mobile products have already decided to integrate Kotlin Multiplatform into their projects. McDonald’s, Netflix, Phillips, and Bolt are just a few examples. Recently, even Google announced that their Google Docs app is now using KMP, and they plan to migrate more of their products like Gmail and Google Drive.
Kotlin Multiplatform is definitely the future of native mobile development. It’s the first time we don’t need to rewrite our native applications or introduce any new framework or language to leverage the benefits of cross-platform development. KMP integrates seamlessly into any native application at any stage of development.
You don’t need to spend months building a new application from scratch. Start delivering new features using KMP right away and push them to production in the upcoming sprints. Meanwhile, migrate your existing code to Multiplatform, simplifying its maintenance and future modifications. And you can achieve all of this while keeping your app 100% native, with the best performance, the best user experience, and full access to all the system APIs and native SDKs.
About the author
Want to migrate your project to Kotlin Multiplatform?
Together, we will make it a smooth and successful process.