How to Blend the Benefits of Cross-Platform Solutions to Existing Native Apps
Write once, run anywhere
The popularity of cross-platform solutions is increasing due to the many benefits they offer to businesses and developers. One significant advantage is the ability to reduce costs and time-to-market by creating a single codebase that works on multiple platforms. This approach also simplifies maintenance and updates because developers can modify a single codebase instead of multiple versions.
Consistent behavior across all platforms is another crucial benefit of cross-platform solutions. Achieving consistency with native applications can be challenging since they are built using platform-specific programming languages and frameworks.
However, if you have a native app for iOS and Android and want to add functionalities to the existing app, it may not be necessary to create a brand new cross-platform app from scratch. Integrating cross-platform modules into existing native applications can be more beneficial, providing some of the advantages of cross-platform development without developing a standalone app.
Incorporating cross-platform modules into existing native applications offers several benefits, including improved app performance, reduced development time and costs, and increased scalability. CashApp, Quizlet, 9GAG, and Netflix are among the high-profile companies that have already embraced this approach. In our latest blog post, we discuss how to write cross-platform modules that can be integrated into existing native applications, enabling businesses and developers to leverage the benefits of cross-platform development while still taking advantage of their existing native applications’ strengths.
What did we choose?
At Applandeo, we utilize several technologies, including Kotlin, Swift, Flutter, Xamarin, and React Native, within our mobile team. To address our code-sharing issue, we naturally turned to one of these technologies. Initially, we ruled out C++ due to the scarcity of mobile developers with the appropriate knowledge and willingness to write in this language. Furthermore, the Dropbox case supported our decision to avoid C++ for code sharing.
Regarding Xamarin, we opted not to use it due to the size of its community, which is comparatively smaller than other available solutions. We believe this could indicate a decline in technology, leading us to question its long-term viability.
After narrowing down our options to Flutter, KMM, and React Native, we chose to compare two of them for our article, forcing us to give up on one. Initially, all three technologies met our selection criteria, including ease of adding a module to an existing project, possible communication between native and shared code, community size, open-source projects, and our developers’ familiarity with the technology.
Ultimately, we decided against React Native, not due to any technical flaws compared to KMM or Flutter but because of our developers’ availability and willingness to analyze the problem. In other words, our subjective assessment took precedence. We welcome React Native developers to discuss their views on the technology and its potential advantages over other solutions. In the future, we may consider testing other options and creating additional parts of the article.
KMM versus Flutter
Upon selecting KMM and Flutter as the technologies to examine for mobile platform code sharing, we proceeded to extract a module in our FreeQuest project that we plan to rewrite utilizing these technologies. We thoroughly scrutinized and compared multiple aspects of these technologies.
Dev team size:
Incorporating KMM into the project does not necessitate an expansion in the team size since an Android developer who is adept in the Kotlin programming language can construct a module and advance its development. Conversely, with Flutter, if we lack someone with proficiency in this technology, we must enlist another individual, which enlarges our team size and introduces added complexity by integrating another programming language into the project stack.
The magnitude of the community has an effect on the pace of application delivery, and it can also play a role in the ease of finding developers for the project, particularly when establishing a team from scratch. A larger community implies that there are more individuals in the job market who are knowledgeable about technology. Here is a contrast of the communities:
- GitHub stars: Flutter (150k) vs KMM (Kotlin repository 44k)
- Number of plugin downloads for IDEs: Flutter (13.2 million for Android Studio + 5.7 million for VS Code) vs KMM (187k)
- Google Trends shows that Flutter is clearly winning over KMM.
Having good documentation can significantly reduce the problems faced by less experienced developers. Not everyone on our team has years of experience, so clear documentation allows us to assign tasks to developers who may be less experienced.
Unfortunately, Flutter’s documentation in this area is lacking. While it provides guidance on integrating a Flutter project with a native app and creating a view in Dart, calling a single method of a service can become complicated, especially for those unfamiliar with “Streaming Data Across Platform Channels on Flutter.” Without the Pigeon tool, which was mentioned earlier, implementing this can be a tedious and frustrating process, especially for less experienced Flutter developers.
On the other hand, Kotlin Multiplatform was designed to make code sharing as straightforward as possible, and the documentation reflects this. If the documentation is insufficient, there are plenty of examples and step-by-step articles on GitHub that can be relied upon.
Using in a Native Project:
KMM’s seamless integration with existing projects, thanks to its use of Kotlin as a native module, sets it apart. Additionally, KMM’s support for concurrency through the use of coroutines is another notable advantage. These coroutines can be mapped to async/await on iOS and reused on Android.
On the other hand, Flutter’s primary challenge lies in communicating with native modules. However, with the help of external libraries such as Pigeon, complex implementation parts can be generated. For more in-depth technical details and implementation insights, please refer to the second part of the article.
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)
Flutter: Business logic + UI
In both solutions, we can share business logic and UI, so no points are awarded to either solution.
Regarding debugging, KMM has an advantage since the Android module can be debugged like any other native module. However, there are some challenges when it comes to debugging the iOS module, where we need to use AppCode or a plugin for Xcode. In contrast, with Flutter, we can start debugging our code with just one command, regardless of whether we use Visual Studio Code or Android Studio. During our tests, debugging KMM code was less problematic than Flutter, even though we did not experience any issues with Android.
Overall, our findings indicate that KMM outperforms Flutter in most cases. While the difference in scores may appear minor, the weight of each point may differ depending on the context and needs of different individuals and teams. Moreover, we must consider the subjective evaluation of the overall experience of working with shared code between platforms. With Flutter, we found that mapping the code between platforms while working on shared code was cumbersome, especially when there were numerous references and communication between the shared module and platform.
In contrast, KMM allows developers to work with a native module, which makes it easier and more intuitive to work with native code, and requires less maintenance compared to Flutter.
Share or not share? If yes, then what?
After understanding the strengths and weaknesses of KMM and Flutter in the context of code sharing, the question arises: is it worth it, and if so, in what circumstances? While phrases like “write once, run anywhere” and “DRY” sound appealing, maintaining such code may prove complex or impossible later on, as seen with J2objc.
Despite this, our answer to “Is code sharing worth it?” is “yes, but in specific situations.” We do not advocate for maximal code sharing as more shared code does not necessarily equate to better outcomes.
Certain elements, such as the user interface (UI/UX), should not be shared as users are accustomed to certain behaviors and control appearances specific to each platform. Similarly, hardware functionality requires native access to the SDK, and code that differs on each platform should be duplicated for platform-specific requirements.
Code sharing works best for standalone modules, which can be treated as separate entities with shared dependencies through configuration classes. Examples of such modules include those for handling external API requests, database modules, models used throughout the project, and validators.
Kotlin Multiplatform is not limited to creating standalone cross-platform apps. It can also be utilized as a framework to add new features to existing native applications, which can accelerate development and reduce maintenance costs. This approach allows developers to take advantage of the benefits of cross-platform development without having to create a new app from scratch.
By combining the strengths of native development with cross-platform capabilities, businesses and developers can achieve improved app performance, faster development cycles, and reduced costs. As the demand for seamless experiences across different platforms continues to grow, cross-platform solutions like Kotlin Multiplatform are becoming increasingly essential for app development.