How to Blend the Benefits of Cross-Platform Solutions to Existing Native Apps
Learn how to integrate cross-platform solutions to your existing native apps and reap their benefits with this comprehensive guide. Discover tips and strategies to maximize your app's functionality and reach.
Cross-platform solutions are becoming increasingly popular due to their numerous benefits for businesses and developers alike. One of the key advantages is the ability to save costs and decrease time-to-market by creating a single codebase that can run on multiple platforms. This approach also enables easier maintenance and updates, as developers only need to modify a single codebase rather than multiple app versions.
Another significant benefit of cross-platform solutions is the ability to achieve consistent behavior across all platforms, which is crucial for providing a seamless user experience. This consistency can be challenging to achieve with native applications, which are built using platform-specific programming languages and frameworks.
But what if you have a native app for iOS and Android and want to add functionalities to the existing app? After all it’s not always necessary or appropriate to create a brand new cross-platform app from scratch, especially if the native app is perfectly functional. In some cases, it may be more beneficial to integrate cross-platform modules into existing native applications, providing some of the benefits of cross-platform development without the development hours to produce a standalone app.
By incorporating cross-platform modules into existing native applications, businesses and developers can achieve a variety of benefits, including improved app performance, reduced development time and costs, and increased scalability. Several high-profile companies have already jumped on the bandwagon including CashApp, Quizlet, 9GAG, and Netflix. In this article, we will explore how to write cross-platform modules that can be integrated into existing native applications, enabling businesses and developers to take advantage of the benefits of cross-platform development while still leveraging the strengths of their existing native applications.
What did we choose?
At Applandeo, we use a few technologies on the mobile team, such as Kotlin, Swift, Flutter, Xamarin and React Native. That’s why the natural solution to our problem is to use one of these technologies for code sharing. Clearly we dropped C++ at the beginning. Mobile developers don’t often choose this technology, which makes it challenging to find professionals with the appropriate knowledge and willingness to write in this language for mobile platforms. Another reason that confirms our opinion of not choosing C++ for sharing code is the Dropbox case.
We decided not to choose Xamarin due to the size of its community compared to other solutions. In our opinion, this indicates that the technology may be in decline, and we have concerns about its long-term viability.
We were left with technologies such as Flutter, KMM and React Native for comparison, but before writing the article, we decided to choose only two technologies, which meant we had to give up one of them. At first glance, each of them met the following criteria:
Adding a module to an existing project is relatively simple
Possible communication between native code and shared code
Size of the community
Number of open-source projects
Familiarity with the technology by our developers
Finally, we gave up on React Native. Objectively, React Native does not have clear technical drawbacks compared to KMM or Flutter, but we based our decision on the availability and willingness of our developers to analyze the problem. In other words, a subjective assessment prevailed. If React Native developers have a different opinion and see an advantage over other technologies that would allow us to solve the problem described earlier, we invite them to discuss it. Perhaps in the future, we will create additional parts of the article where we consider testing other solutions.
KMM versus Flutter
After choosing KMM and Flutter as the technologies we want to explore for code sharing between mobile platforms, we decided to extract a module in our FreeQuest project that we will rewrite using these technologies. We analyzed and compared several aspects.
Dev team size
Adding KMM to the project does not result in an increase in the team size because a person who natively writes Android code knows the Kotlin programming language and can create such a module and then develop it. In the case of Flutter, if we don’t have a person who knows this technology in the project, we must add another person, which increases our team size and adds another level of complication, which is adding another programming language to the project stack.
The community’s size impacts the speed of delivering the application and can also be a factor in how easily we can find developers for the project if we are just starting to build a team. A larger community means more people familiar with this technology in the job market. Here’s a comparison of the communities:
GitHub stars: Flutter (150k) vs KMM (Kotlin repository 44k)
The better the documentation, the fewer problems for less experienced developers. We don’t always have people with years of experience on the team, so with good documentation, we can assign the task of extracting shared code to a developer with less experience.
The documentation for Flutter in this area practically doesn’t exist. It clearly shows how to add a Flutter project to native apps and how to open a view written entirely in Dart. However, when we want to call a single method of some service, the problem becomes more complicated for people who were not previously familiar with “Streaming Data Across Platform Channels on Flutter.” Implementation can become tedious and frustrating without Pigeon, especially for less experienced developers in Flutter.
Kotlin Multiplatform was created to make code sharing as easy as possible so the documentation is clear and straightforward. If the documentation is not enough to help us, we can rely on many examples of such projects on GitHub or articles that explain how we can do it step by step.
Using in a Native Project
Since KMM uses Kotlin and is treated as a native module, adding it to an existing project is not a problem. KMM’s most significant advantage is its native support for concurrency. In a KMM project, we can use coroutines, and in native projects: on iOS, they are mapped to async/await, and in the case of Android, coroutines can be used again.
The biggest challenge with Flutter is communication with native modules, but fortunately, external libraries, such as the previously mentioned Pigeon, allow us to generate more problematic parts of the implementation. If you are interested in technical details and implementation, please read the second part of the article, discussing this issue.
External library support
Since not all Kotlin libraries support KMM, Flutter has a significantly larger library base, which is an advantage for Flutter.
KMM is still in beta, but version 1.0 will be released this year, while as of the writing of this article, Flutter is already at version 3.7.3.
What can we share
KMM: Business logic + UI (Compose Multiplatform, although for iOS, it’s currently in the experimental phase)
Flutter: Business logic + UI
In both solutions, we can share business logic and UI, so no points are awarded to either solution.
When it comes to debugging, KMM has an advantage because the Android module can be debugged just like any other native module. However, the problem arises with iOS, where we need to use AppCode or a plugin for Xcode. In the case of Flutter, regardless of whether we use Visual Studio Code or Android Studio, we can start debugging our code with just one command. During our tests, we did not notice any problems with Android, but debugging was often interrupted in the case of iOS. Based on our experience, debugging KMM code is less problematic.
In our findings, KMM outperforms Flutter in most cases. Judging by the score alone, the difference may seem minor. However, it is essential to remember that each point may carry a different weight for different people in different situations. We also cannot ignore the subjective evaluation of the overall experience of working with shared code between platforms. With Flutter, it was clear that we were mapping the code between platforms while working on shared code. Therefore, the more references and communication between the shared module and platform, the more maintenance and work on such code becomes cumbersome.
On the other hand, KMM allows for working with a native module, which makes it easier for developers to work with and gives them a sense of working with native code.
Share or not share? If yes, then what?
Knowing the strengths and weaknesses of KMM and Flutter in the context of module sharing, we must ask ourselves if it’s worth it. If so, what exactly? In what circumstances?
Phrases like “write once, run anywhere” and “DRY” sound very nice, but in practice, maintaining such code later on may prove to be much more complex and sometimes impossible due to technology abandonment, for example — J2objc. This ultimately means we will spend more time and money on it than we would with two separate native implementations.
Nevertheless, our answer to “Is code sharing worth it?” is “yes, but in specific situations.” We are far from saying that the more shared code, the better.
What we shouldn’t share:
User interface (UI/UX): Users of each system are accustomed to certain behaviors and control appearances, so we should use their native versions for each platform.
Hardware: Often, system overlays change the behavior of some phone components, so we need native access to the SDK, for example, Xamarin used enums to change the TTS flag, while the native SDK used int and the overlay used its own value, which was an INT.
Logic that differs to some extent on each platform. In this case, we duplicate the code for each platform to be able to adjust it to each platform’s specific requirements.
Sharing code between different platforms works best for standalone modules. We treat these modules as separate entities whose dependencies on the main module we can share through configuration classes. Examples of such standalone modules include modules for handling external API requests, database modules, models used throughout the project, validators, etc.
Kotlin multiplatform can serve as a framework to add functionalities to existing native apps without the need for a standalone app. This can streamline development by reducing the time it takes to add new functionalities to an app, making maintenance as well as time to market much less resource intensive. While a new cross-platform app may not always be necessary or the best choice, integrating Kotlin Multiplatform modules into existing native applications can provide many of the benefits of cross-platform development without requiring a cross-platform app from scratch.
By leveraging the strengths of both approaches, businesses and developers can achieve improved app performance, reduced development time and costs, and increased scalability. With the increasing demand for mobile apps and the need to provide seamless experiences across different platforms, cross-platform solutions are becoming an increasingly essential tool for businesses and developers.
Are you looking for a tech partner? Searching for a new job? Or do you simply have any feedback that you'd like to share with our team?
Whatever brings you to us, we'll do our best to help you. Don't hesitate and drop us a message!