Introduction
The Very Good Security (VGS) Collect iOS and Collect Android SDKs are powerful tools designed to enable the secure collection of sensitive user data. In an age where privacy, compliance, and data security are paramount, these SDKs play a crucial role in helping developers handle sensitive information like credit card numbers, social security numbers, and personal identification information.
Challenges and the Need for Conversion
While both SDKs offer an incredible development experience for customers, the fact that we need to maintain separate codebases for iOS and Android has always made us think that this is an area that could benefit from a single codebase. The need to ensure feature parity, consistent performance, and simultaneous updates can lead to increased development time and resources when maintaining multiple code bases.
Understanding the Original Codebase
Our Collect iOS SDK is written on Swift. We use Swift PM and CocoaPods for distribution.
The VGS Collect Android SDK is mostly developed using Kotlin, for building and managing the project we use Gradle and Maven for distribution.
Evaluating Kotlin Multiplatform
The decision to experiment and convert core functionalities from the individual Collect iOS and Android SDKs into Kotlin Multiplatform stemmed from these challenges of maintaining the disparate code bases. By leveraging the shared code capabilities of Kotlin Multiplatform, the development process can become more efficient, consistent, and maintainable.
We choose KMM above other frameworks, such as React Native and Flutter, because of its unique combination of features:
- Code-Sharing Across Platforms: One of the key drivers behind considering Kotlin Multiplatform was the ability to share code across iOS and Android platforms.
- Native Performance: Unlike some cross-platform solutions, Kotlin Multiplatform compiles down to native code for each platform. This ensures that the performance is on par with native development, avoiding any compromise in responsiveness or user experience.
- Integration with Existing Code: UI is a crucial part of our SDKs. It was important to keep native UI and don’t create breaking changes for existing users.
Before the Conversion Process
Before starting to evaluate a shared KMM module, we identify core functionalities that could be transitioned. We highlighted the following SDK areas as prime candidates for conversion:
- Networking: Managing network requests and responses is a foundational part of the SDKs. Converting networking code to KMM can enhance maintainability and efficiency.
- Data Validation: Validating user-inputted sensitive data is paramount to the functionality of the SDKs. A shared approach to data validation through KMM can standardize the validation logic, rules, and error handling, minimizing potential discrepancies between iOS and Android implementations.
- Card Brand Detection: We use regular expressions to detect Card Brands. Converting this component to KMM can lead to a unified detection mechanism, streamlining the process and reducing the risk of inconsistencies between the platforms.
- Analytics: The analytics component gathers insights into user behavior and SDK performance. By transitioning this part to KMM we can ensure analytics consistency across iOS and Android platforms.
The Conversion Process
We initialized a new KMM module within our project. The shared code, intended for both iOS and Android, was placed in the commonMain source set. Platform-specific implementations were housed in the androidMain and iosMain source sets, respectively.
While the core functionalities were now in the shared codebase, there remained several platform-specific features and integrations in the SDKs. To manage this, we made use of Kotlin Multiplatform's interoperability with Swift for iOS and Java/Kotlin for Android. This allowed our KMM module to work seamlessly alongside existing native code.
Integrating KMM into the SDKs
For Android, the integration was relatively straightforward. The KMM module was treated as any other module in the Android project. However, for iOS we utilized Kotlin/Native to compile the shared code into a framework. This framework was then integrated into our iOS SDK using CocoaPods.
Challenges and Solutions:
As with any technological shift, integrating KMM into our iOS project presented certain challenges. While the promise of shared code and improved efficiency was appealing, the implementation process revealed some obstacles.
1. Issues with Enums
One of the challenges encountered involved the handling of Kotlin enums in the iOS project. In Kotlin, we can define enumerations with associated properties and methods, but these do not map directly to Swift or Objective-C enums, which have a simpler structure.
When exposed to Swift through Kotlin/Native, Kotlin enums appear as separate classes, with each enum case becoming a class instance. This discrepancy required us to implement workarounds in our Swift code to accommodate the representation of Kotlin enums.
2. Code Documentation for KMM Objects
In the process of integrating KMM into our iOS project, we realized that accessing code documentation for KMM objects within the iOS environment was problematic. Unlike the usual experience in Xcode, where developers can easily view documentation by selecting a specific object or method, KMM objects did not provide the same level of convenience.
When working with native Swift or Objective-C code, Xcode provides "Quick Help" by simply Option-clicking an identifier. However, for KMM objects, this feature did not provide the expected documentation. The lack of easily accessible code documentation created friction in the development process, as developers had to refer to external sources or the original Kotlin code for documentation, adding an extra layer of complexity and time overhead.
3. Interoperability Considerations
While KMM offers interoperability with Swift and Objective-C, certain Kotlin features and paradigms do not have direct equivalents in these languages. This occasionally necessitated additional bridging code or adaptations in the iOS project to ensure smooth communication between the shared KMM module and the existing iOS codebase.
4. Beta status of KMM
A significant factor in our decision-making process was the fact that KMM was still in beta at the time of our evaluation. For many organizations, adopting beta software may be acceptable. However, as providers of an enterprise-level SDK, stability and reliability are paramount.
Conclusion
Kotlin Multiplatform Mobile (KMM) offers a promising approach to shared code across mobile platforms, making it an attractive solution for many applications. In particular, for companies developing their own applications from scratch, KMM can bring substantial benefits in terms of code-sharing and streamlined development.
However, as creators of SDKs, we must consider the impact of KMM on our customers' development experience. The challenges we encountered in integrating KMM into our iOS project, such as the absence of accessible code documentation, hindered our ability to provide the optimal development experience that our customers expect. Furthermore, interoperability limitations may restrict the full utilization of Swift's features, and any workarounds we implement could introduce breaking changes for existing customers.
Given these considerations, we have chosen to postpone the implementation of a shared KMM module in our SDKs. While the benefits of KMM are evident, the current challenges present significant obstacles that must be addressed to ensure a seamless integration that enhances, rather than detracts from, our customers' development experience.
We remain open to the potential of KMM as a solution for our SDKs. As the KMM framework continues to evolve and mature, we are prepared to re-evaluate the feasibility of integrating a shared KMM module into our SDKs, considering any updates and improvements that emerge.
Note: This article was written based on Kotlin Multiplatform Beta 1.7.20. Kotlin/Native interop with C and Objective C Beta 1.3.