Mobile
Pincode Migration Journey with Compose Multiplatform
Chethan N, Pincode Business App Team04 August, 2025
In today’s diverse technological landscape, the ability to create applications that seamlessly operate across multiple platforms is a significant advantage.
At Pincode, our seller app, was initially created using Android SDKs with Compose UI in Kotlin, and catered exclusively to Android users. Recognizing the need to support sellers who prefer managing their activities (such as cataloging, order management and billing) on desktop machines (primarily on Windows), we embarked on a journey to build a desktop application.
Our solution? Compose Multiplatform (CMP), which allowed us to reuse existing and newly-built features on desktop with minimal effort.
We also wanted to keep the option of releasing an iOS app open so that it can be released if needed by our seller partners. As most sellers use Android / Windows devices, we prioritized these platforms in the first go.
This blog details our experience, covering the rationale behind choosing CMP, the challenges we faced, and the lessons we learned.
How did the CMP journey begin?
The journey began when we presented the idea of using CMP to build our iOS app to the product team. However, they were keen on building the desktop solution..
The desktop app exploration started off as an additional offering, along with Android. But later became a critical solution for our seller partners as we wished to provide the complete POS (point of sale) features as part of the desktop app. This involved supporting their offline sales, billing , etc. along with order and catalog management.
Why choose Compose Multiplatform for desktop?
When considering a cross-platform solution, several factors came into play. We evaluated traditional desktop UI frameworks and other cross-platform options before settling on Compose Multiplatform. Here’s why:
Benefits Over Traditional Desktop UI Frameworks: CMP enables the sharing of both, business logic and UI, across platforms. This “write-once-run-anywhere” approach significantly reduces development time and eliminates the need for maintaining separate codebases.
Comparison with other cross-platform options:
React Native: We opted against React Native due to its web-first approach. This would have created a skill gap within our team and could’ve introduced potential performance overhead that it comes with.
Flutter: Flutter’s high learning curve for the Dart language, difficulty in hiring Dart engineers, and limited community support made it less appealing.
Webview: The high-performance overhead and drawbacks similar to React Native led us to dismiss Webview as a viable option.
Below are a few other factors we considered that nudged our decision toward CMP:
Stability: Compose Multiplatform was stable on both Android and Desktop platforms, making it a reliable choice for our needs. CMP for iOS was in beta when we made our choice and started our CMP journey. As of May 2025, CMP support for iOS is stable and production-ready starting from version 1.8.0.
Kotlin Advantage: Our existing Android app was already written in Kotlin, which made the transition to Kotlin Multiplatform much smoother. This also meant that our developers are already comfortable with Kotlin stack and building systems, and can easily start contributing to CMP apps.
Backing of JetBrains and Google: CMP being supported by companies with a track record of delivering exceptional developer tools made it a reliable and future-proof choice for our team.
Once the decision was made to use CMP, the planning and execution involved various choices and the long journey of making our current android app code multi platform compliant began. The next section explains the journey.
Setting up the project and repository migration
We decided to convert our existing android repository to a multiplatform one and took care of intermediatory state where we had both android-only modules and KMP/CMP modules. This allowed the release of the Android app from the same repository, while a couple of folks from the team embarked on a journey of migrating the entire app module-by-module to CMP/KMP. This also meant the developer could easily contribute to KMP modules while maintaining Android-only modules without a repository switch. We merged these changes to our default branch in our source control system. This process of contribution by all developers to the KMP modules was seamless.
Below are the steps we took to migrate our Android-only app to CMP.
- Created a minimal Compose Multiplatform (CMP) “Hello World” app. This targeted Android, iOS (via an Xcode project), and Desktop, all within the same project/repository.
Note: CMP web support using WebAssembly is in alpha (at the time of writing this blog). One can explore this by creating a web target as well. - Developed an umbrella module. This module should contain:
- Shared code
- The root Compose view with a NavHost for navigation.
- Migrated each module to CMP/KMP: while keeping existing Android modules intact.
- Used kmp- or cmp- prefixes for the new KMP counterparts of the android only modules. This is to manage dual dependency graphs for Android and KMP.
- Completed the migration of all modules and features to CMP/KMP. Released across platforms and stabilized the build.
Note: For Android, one can manage data migration for Room and DataStore to KMP for a seamless user upgrade experience. - Remove Android-only modules from the repository after the full transition.
These steps facilitated the transformation of our Android-only repository into a multiplatform one, leveraging CMP to target Desktop and iOS in addition to Android.
Some of the KMP / CMP libraries that we used to achieve this:
CMP material3 adaptive layouts: For adaptive UI design
- Ktor: For networking – Replacing OkHttp
- Koin: For dependency injection – Replacing Hilt
- KMP versions of Jetpack libraries: Room, ViewModel, and DataStore – Replacing Android specific ones
- Coil: For image loading – Replacing Glide
- KotlinX serialization: For JSON serialization and deserialisation
- Sentry: For crash reporting, especially on desktop as we don’t have Crashlytics support
Building the multi platform UI with Compose
Compose Multiplatform significantly simplifies UI development by allowing the use of shared Compose UI code across multiple platforms. Here’s how we tackled UI development and addressed some key challenges along the way:
Compose UI: Compose Multiplatform makes compose APIs available from common Kotlin code, allowing you to write shared compose UI code that can run on Android, iOS, desktop, and web.
Key Challenges:
Platform-Specific Resources: Handling platform-specific strings, icons, and back button behaviors required workarounds. We migrated Android resources to multiplatfrom resources, this required changes related to Android only constructs like R files (used to refer string, fonts and other resources in Android) which can’t be used in common code.
Adaptive Design: Ensuring the UI adapts seamlessly to different form factors (desktop vs. mobile) was crucial.
Solution:
- Adaptive Design Principles: We adopted a 12-column-grid-system, inspired by Bootstrap, to manage different screen sizes and layouts effectively.
- Dynamic Navigation: The UI dynamically switches between a static left navigation bar on desktop and a hamburger menu on mobile based on screen size.
- Adaptive Design System: Our design system was rebuilt to support multiple window sizes, using a semantic layer that maps different values based on the current window size.
Platform specific Considerations
Addressing platform-specific considerations was crucial for delivering a seamless user experience. We focused on several key areas:
Distribution and updates:
We chose to bypass the Windows Store to retain full control over update delivery.
Squirrel was used for first-time installs and silent updates.
Initial installation is streamlined through a bundled “setup.exe” file that includes all necessary dependencies, enabling a simple one-click setup process.
The app silently checks for updates and downloads, if any, in the background. Post that it prompts users to restart and update or be reminded later.
Recommendation to any team planning on trying CMP
When planning to adopt Compose Multiplatform (CMP), it’s crucial to begin with empathy and education. Recognize that introducing new technology can be challenging. A couple of team members should delve deeper into understanding CMP, including how Kotlin Multiplatform (KMP) works on each target platform and its benefits like code reuse. This knowledge should then be shared with the broader team. Explaining that platform-specific code can still be written using the expect/actual mechanism can build confidence, especially among those unfamiliar with Kotlin, like some iOS developers. It’s also vital to address concerns proactively by creating a central FAQ and establishing a dedicated communication channel for CMP support and guidance.
To further build confidence and demonstrate CMP’s capabilities, offer proof with a sample project. This could involve migrating an existing feature or building a new, representative one. Following this, plan the migration carefully. Start with a foundational “Hello World” application targeting Android, iOS, and Desktop. Develop an umbrella module to house shared code, the root Compose view, and necessary iOS pre-build configurations. The migration of existing modules should be incremental, keeping Android modules intact initially, and using clear prefixes like kmp- or cmp- for new multiplatform modules. Release CMP-built screens under feature flags to allow for fallback options and incremental testing, and ensure data migration is managed for a seamless user experience.
Finally, focus on UI adaptability by adopting adaptive design principles to support various form factors; optionally, a 12-column grid system and dynamic navigation can be used to handle different screen sizes effectively. Leverage key libraries to streamline development, such as CMP “material3” adaptive layouts for responsive UI, Ktor for networking, and Koin for dependency injection. It’s important to understand and communicate that while CMP might have a higher initial fixed cost, it can lead to lower variable costs for subsequent feature development, offering long-term benefits.
Some of the challenges:
The CMP community is fast growing and has a lot of library and community support (joining the Kotlin communities is the best way to get updates and community support). However, it is not as big as the Android / React / iOS community yet. At times you might not get an off-the-shelf solution and have to build stuff yourself.
At times, we had to come up with our own solutions like building a simple version of KMP equivalent to Chucker, 12 span layout approach for our adaptive design solution.
Future
- Try out CMP in more apps at PhonePe and see if it meets required standards on iOS.
- Figure out iOS-specific concerns, workaround and limitations and pave the way for CMP adoption in iOS.
- Work with the growing CMP community in enhancing the platform capabilities.
- Extend mono repository approach: Share the same repository for multiple apps targeting multiple platforms.
- More active contribution to Open source community
Conclusion
Building a cross-platform desktop application with Kotlin Multiplatform and Compose has been good decision for Pincode. By leveraging CMP, we’ve successfully unified our development efforts, reduced code redundancies, and delivered a consistent experience to our sellers across different platforms. As we move forward, we continue to explore further enhancements and extend our reach to additional platforms, reinforcing our commitment to innovation and efficiency.