Skip to content
CipherCruCipherCru

Menu

Cross Platform App Development

Cross Platform Modernization

A shared codebase three major versions behind is not saving you anything. It is one store deadline away from being two apps you cannot ship.

Typical duration
Four to twelve months
Team shape
Two to five senior engineers
Starts with
A three-week assessment
An ageing shared codebase under repair across two platforms

The upgrade treadmill is the price of a shared codebase

React Native and Flutter both move quickly, and a codebase that skips upgrades compounds the problem: each deferred version makes the next one harder, native dependencies fall out of maintenance, and eventually a store requirement lands on a framework version that cannot meet it.

The way out is not a rewrite. It is a sequence: restore the ability to build and ship, then move the framework one version at a time with the app verified in the field at each step, then deal with the dependencies that were blocking it. Feature work continues throughout, and the end state is a codebase where upgrading is routine rather than a project you have to schedule.

What usually goes wrong, and what we do instead

The problem compounds quietly, and then arrives with a deadline attached.

Where shared codebases fall behind

  • An upgrade deferred once, then repeatedly, until the gap is a project
  • A native dependency abandoned upstream and blocking every version bump
  • A build that only works on one machine belonging to someone who left
  • No tests, so nobody can tell what a version bump actually broke
  • Platform conditionals everywhere, so the shared code is not really shared

How we run it instead

  • The ability to build and ship restored before anything else is touched
  • One framework version at a time, verified in the field at each step
  • Abandoned dependencies replaced or brought in-house and owned
  • Characterisation tests written before behaviour is changed
  • The boundary redrawn deliberately, so conditionals stop accumulating

What changes, in practice

Not a version number. The things that are different for the people who build and ship the app.

The codebase today

Working, and stuck.

  • The framework is several major versions behind
  • An upgrade attempt was tried once and abandoned
  • Some dependencies have no maintained upgrade path
  • The build works on one machine and nobody is sure why
  • There are no tests, so a version bump is an act of faith
  • A store deadline would be an emergency for both platforms at once

The codebase after

Same product, current.

  • The framework is on a supported version, with a cadence agreed
  • Upgrading is routine maintenance rather than a project
  • Every dependency is maintained, replaced, or owned by you
  • The build runs from a clean checkout in CI, reproducibly
  • Behaviour is pinned by tests written from the original app
  • Store requirements are tracked, so a deadline is never news

What the work involves

Modernizing a shared codebase is five activities, in a deliberate order.

Restoring the release path

A reproducible build in CI producing both store artefacts, before anything else is touched.

Characterisation

Tests written against what the app actually does, so a version bump has something to fail against.

Dependency recovery

Abandoned packages replaced, or brought in-house and owned, rather than pinned indefinitely.

Framework upgrades

One major version at a time, each shipped and verified in the field before the next begins.

Boundary repair

Platform conditionals pulled back behind a documented native boundary rather than left scattered.

Device range recovery

Re-established support for the hardware your users carry, tested rather than assumed.

How the programme runs

The same five stages as every engagement, applied to a codebase that cannot stop shipping.

Diagnose

Whether you can ship today, how far behind the framework is, and which dependencies block the next version.

Decide

The upgrade path, the dependency decisions and the order of work, sequenced by risk rather than appetite.

Prove

One release shipped from a clean CI build to both stores, before any version is changed.

Deliver

Versions and dependencies moved in slices, each verified in the field before the next.

Hand over

Your team performs an upgrade with us watching, then without us, on the cadence we agreed.

What we work with

The starting point is whatever is already there. This is the range we bring codebases up to.

Shared

  • React Native
  • Flutter
  • TypeScript
  • Expo

Native

  • Kotlin
  • Swift
  • Gradle

Data and services

  • SQLite
  • Firebase
  • PostgreSQL
  • GraphQL

Delivery

  • GitHub Actions
  • TestFlight
  • Sentry
  • Datadog

What you get, and when

Handed over as it is produced, not assembled at the end.

  • Whether you can ship today, and what it would take to
  • The upgrade path, version by version, with what blocks each
  • A decision per unmaintained dependency: replace, own, or remove
  • A reproducible build running in CI from a clean checkout
  • Characterisation tests pinning the original behaviour
  • A staged release to both stores you can halt
  • A framework upgrade cadence your team can actually follow
  • Crash and performance dashboards split by platform and version
  • The redrawn boundary, documented, with what belongs on each side

What we work towards

ILLUSTRATIVE, pending sign-off. These are the measures this work is judged on and the direction they should move — not results from a named engagement. They will carry real figures or be removed before launch.

Framework versions behind

Several majorsCurrent, on a cadence

Time to ship a hotfix

WeeksA day

Unmaintained dependencies

Blocking upgradesReplaced or owned

Machines that can build a release

OneAny, via CI

Behaviour covered by tests

NoneThe paths that matter

Questions about cross-platform modernization

The ones we are asked most often before a first conversation.

How is this different from Cross Platform Migration?
A migration changes the platform strategy — two native apps onto one codebase, or one back onto two. This keeps the strategy you already chose and brings the codebase back onto a supported framework version. If you are behind on versions and also questioning whether the shared codebase still earns its place, the assessment covers both and will tell you which problem to solve first.
Would it be quicker to start again?
Rarely, and the reason is that a rewrite loses the behaviour nobody documented — the edge cases and workarounds that accumulated over several years and that users now depend on. Incremental upgrades keep the app shipping throughout, so a bad step costs one version rather than a programme. For a genuinely small app the calculation can go the other way, and the assessment gives you a straight answer.
We cannot ship a release at all right now. Where do you start?
There, and before touching a single version number. Restoring the ability to build and ship is the first slice, because until it is done every other fix is blocked behind it, including a security one. That usually means a reproducible CI build producing both store artefacts, and one staged release proving the path end to end.
One of our dependencies is abandoned. What happens to it?
One of three things, decided explicitly during the assessment: replace it with a maintained equivalent, fork it and bring it in-house so you own it, or remove the feature that needs it. What we will not do is pin it and route around it, because that is how the codebase got stuck in the first place. Whichever route, the decision is yours with our recommendation attached.
Do we have to pause feature work?
No, and we would resist it. A freeze is the assumption that breaks these programmes, because the business cannot honour it for as long as the work takes. Upgrades are sequenced around the roadmap. A feature landing during a version bump takes a little longer, which is a schedule conversation rather than a stop.
How do we avoid ending up here again?
An upgrade cadence agreed and actually followed, which is a scheduling commitment more than a technical one. Both frameworks release regularly and the cost of staying close to current is small and steady, where the cost of catching up is large and lumpy. We leave you on a supported version with the cadence written down, and your team performs one upgrade with us watching before we go.

Shared codebase that has fallen behind?

Tell us what version you are on and whether you can ship. We will tell you what the path back looks like.

Strictly necessaryEssential for the site to function: page navigation, security, session management, and remembering the cookie choices you make here.
Always on
FunctionalRemembers choices you make, such as language, region or display preferences, so the site opens the way you left it.
Performance and analyticsPerformance and analytics cookies show us how the Website is used: which pages are visited, how long is spent on them, where visitors came from, and what errors occur. They are set by Google Analytics and by HubSpot, whose cookies also link the pages you viewed to any enquiry you later send us.
Marketing and targetingTracks browsing activity to measure advertising and show relevant ads. We set none of these today, and will not without your opt-in.