
This is what cross-platform development is really like

Complex, experimental and occasionally unpopular, but the end result was worth the effort
By Irina Nasedkina, product owner, EXANTE
In 2024, we had two apps that effectively did the same job. EXANTE’s core product is our trading platform, and we were running two versions of it.
The first, developed over a decade, had an enormous number of features written natively for iOS and Android, and while the app had accumulated functionality, its design was outdated. The UX approach was inconsistent within the app and between platforms. What worked one way on iOS behaved differently on Android, creating constant questions and frustration for clients.
The second application was younger and more modern, with a consistent, fresh UI/UX, but had far fewer features than the first. It was also native for both platforms.
We had one app that was rich in functionality but had an ageing, fragmented design. The other was good-looking and contemporary, but functionally limited. And both were native, meaning every change had to be made twice, once for each platform.

Older version

Simpler version

Older version

Simpler version
Two applications (the original and the newer release) doing roughly the same thing, but living parallel lives.
Both were supported by two teams: one for iOS and one for Android, and although development, QA, and testing were shared, the implementations were separate. We were writing the same feature twice: once for iOS and once for Android, and sometimes one feature would end up being rewritten four times: once per platform for each of the two apps.
This caused headaches, particularly for QA and automation engineers who had to account for implementation differences and write separate tests for each platform.
To make things more complex, the same feature could behave differently across platforms because different people built it for each OS. This had to be identified and aligned constantly. And then there are the effects of two native codebases, which means double the code review, twice as many bugs and twice the onboarding effort for new team members.
There is also the matter of team culture.
iOS and Android had been merged into a single team, but while development of both shared a product, processes and an organisational structure, the team never really became one. Different platforms, codebases and implementations of the same features had created a spirit of rivalry. "On iOS, we do it this way." "On Android, we built this." That rivalry persisted.
Decision number one
When we decided to build a new mobile app, our initial plan was to base it on the more modern app and add features from the older one.
A couple of months in, and we thought: what if we built the new application on a shared stack? Theoretically, this could deal with the inconsistency between platform implementations, the different approaches and the complexity of maintaining two native codebases.
So we took one module, wrote it in Kotlin Multiplatform (KMP) and Compose and embedded it in the existing application. This was a deliberately small, real experiment, to see if multiplatform modules integrate and function inside a native application.
We put the new application project on hold until we had the results of the experiment. In parallel, work continued: developing and supporting features in both existing applications and in the experiment itself. We approached it pragmatically, selecting a real feature that needed to be built anyway and shipped it to production straight away. In effect, we ran not just a lab experiment but a full production test of the approach.
The result was positive: the feature built on KMP and Compose went live in one of the applications. That meant the entire new application could be moved to KMP and Compose.
Now the project truly began. We were building for multiplatform.
One configuration to rule them all
Once it was clear we were going multiplatform, we had to make a series of architectural decisions. The first was about modularity and how to manage the feature set across different clients, given that they would need different application configurations.
We considered three approaches.
The first: toggle modules via an external branding service. Configuration happens externally; the application receives the required feature set at build time or runtime.
The second: create two fixed setups with different module configurations and switch between them with a single flag.
The third: manage each module independently, deactivating or activating it within the application itself.
We chose the first: branding-based configuration, as it was the fastest to implement and if more granular module management became necessary, we could extend it later.
As it turned out, complex module states were not something we needed. A simple "module available or unavailable" distinction, however, proved very useful. The choice was right: we did not over-engineer where it was unnecessary, and we saved time.
A simple "module available or unavailable" distinction, however, proved very useful: modules were switched on or off at the branding level, so the client simply checked availability rather than managing an in-app setting — which would have meant redesigning the UI and testing enable/disable behavior separately for every client. The choice was right: we did not over-engineer, and we saved time.
Define once, apply everywhere
The component approach deserves its own section, because it is one of the pillars of the entire architecture.
The principle is straightforward: every UI/UX component in the design is mapped to a component in the code. A component is the application’s basic building block. We compiled a full list of components, aligned it between design and engineering and then everything, from buttons to tables, was built from that list.
The guiding principle during development is reuse. Before creating anything new, we ask: Can an existing component handle this? In most cases, the answer is yes.
Because we have to maintain the existing applications while building the new one, our resources are limited, and this approach saves a lot of time. Component behaviour is defined once and works consistently everywhere. There is no need to redesign, re-specify in requirements, or re-verify in QA each time.
The human factor
Any fundamental change in the development approach means a change for people.
Moving to multiplatform meant that native developers were no longer responsible for a single platform. An iOS developer was no longer "an iOS developer," but was now a developer on a shared platform. People who spent years building expertise in a specific technology had to learn a new platform. It raised many questions and met resistance.
It also meant significant restructuring. One repository instead of two. More people are working in a shared space. New rules covering code structure, code review and decision-making. Everything that had previously been resolved within the iOS or Android team now required wider alignment.
However, the relationship with design became much closer. With a component-based approach, every design decision directly impacts engineering: a new component in a mockup means a new component in code, with its own states, behaviour and documentation. Critically, changing a component means changes across multiple modules. Every module using that component must be checked to confirm that nothing has broken.
In the same vein, development now requires much greater cohesion within teams. Adding a new module is no longer just about writing it; developers must also check whether existing components can be reused. Create a new component, and it’s essential to make sure it fits the architecture and will be useful in other contexts as well. This demands a different level of communication and a different way of thinking.
It has, unquestionably, become more involved, maybe even harder. But it’s also considerably more interesting. And there was a welcome surprise in store.
Working on a single product, towards a unified user experience, feels fundamentally different from building features in parallel across separate applications. Developers began to relate differently to the product and colleagues from other teams. The boundary and competition between iOS and Android is disappearing, to be replaced with a single, unified team.
Ready to launch
There were two release options: launch a new application or ship everything as an update to the existing one.
We chose the update, mainly due to resource allocation. A separate, new application would mean maintaining two products in parallel, managing two audiences and persuading users to switch. An update concentrates on stabilising and developing the new product without spreading resources too thinly. We chose the one-time pain of transition over the chronic pain of dual maintenance.
But an update is always stressful for users. So we paid particular attention to preserving the app’s structure: familiar navigation patterns, habitual feature placement and expected interaction flows. We wanted a user to think: It looks different, but I know where everything is.

Older version

Simpler version

Older version

Simpler version
Tech is the least important part of the story
Like so much we do in development, this story is not just about technology. The real work was changing the way a team thinks, stopping repetition and turning two competing groups into a single, unified workforce.
We’re still updating the app, and users will receive the changes gradually, so it’s hard to say how much faster development has become. What we can say for certain is that now, we write the code once. UI and unit tests are shared, and 85% of the codebase is common to iOS and Android. App behaviour is more predictable, because the apps share a networking layer and interact with the backend in the same way. This means simpler testing and fewer bugs.
The end result is a road map for multiplatform development. Take one new feature. Write it on the shared platform. Deploy it to both applications. You will learn more in a month of doing this than from six months of planning. It was only when we started implementing features on the shared platform that we properly understood how the process should work.
And that is when the real project began.
Ten artykuł jest publikowany wyłącznie w celach informacyjnych i nie powinien być traktowany jako oferta lub zachęta do kupna lub sprzedaży jakichkolwiek inwestycji lub powiązanych usług, do których można się tu odwołać. Obrót instrumentami finansowymi wiąże się ze znacznym ryzykiem strat i może nie być odpowiedni dla wszystkich inwestorów. Wyniki osiągnięte w przeszłości nie są wiarygodnym wskaźnikiem wyników w przyszłości.



