Mobile app modernization: migrating to Jetpack Compose and SwiftUI without rewriting everything

When migrating to Jetpack Compose and SwiftUI pays off and when it doesn't. How to do it gradually alongside product development, which pitfalls to watch out for and roughly what it costs.

Zespół Mobilesoft· Engineering team· 22 października 2026· 8 min czytania

"We should really rewrite this in Compose." If your Android app is more than a few years old, you have probably heard that from a developer. On the iOS side the same sentence is "we need to move to SwiftUI". It usually comes up when a new feature is quoted at twice what you expected, for reasons that are not immediately obvious.

Migrating to Jetpack Compose and SwiftUI is currently the most common form of mobile app modernization. The good news: it does not require rewriting the app from scratch or pausing development for six months. The bad news: if it is poorly planned, it can drag on for years and leave the project with two ways of building screens instead of one. In this article we look at when the migration makes sense, how to do it gradually and what it realistically costs.

What this is actually about

Both platforms have two ways of building a user interface:

  • Android: classic views defined in XML files (the View system) and the newer Jetpack Compose, where the UI is described in Kotlin code.
  • iOS: UIKit, which most apps were built on until roughly 2020, and the newer SwiftUI.

The new approaches are declarative: instead of manually updating every element on the screen, you describe what the screen should look like for a given state and the framework takes care of the rest. In practice that means less code, fewer "the button didn't refresh" bugs and faster development of typical screens.

Let's clear up one myth straight away: neither XML views nor UIKit are obsolete in a technical sense. Google and Apple still support them, and an app built on the older technology will keep working. The direction, however, is clear: new components, documentation samples and tooling are being built primarily for Compose and SwiftUI.

When migrating to Jetpack Compose and SwiftUI makes sense (and when it doesn't)

The migration pays off when the app will continue to be developed. Every new screen built with the new technology is then faster to build and cheaper to maintain, and the difference compounds month after month. Concrete signals:

  • New features take disproportionately long, because every screen needs a lot of boilerplate (list adapters, manual view refreshing, sprawling XML files).
  • You struggle to hire or onboard new people. Junior Android developers today learn mainly Compose, and junior iOS developers learn SwiftUI. The old code starts to become a barrier to entry.
  • You are planning a visual refresh of the app. If you are rebuilding the screens for a new design anyway, doing it in the new technology right away costs little extra.
  • The app has to work well on tablets and foldables. New Android versions increasingly enforce support for different screen sizes, and such layouts are much easier to build in Compose. More on that in our article on what has changed in the latest Android and iOS.

The migration does not make sense when:

  • the app is in "keep it alive" mode and nothing changes in it apart from OS updates;
  • the product is going to be retired or replaced within a year or two;
  • the main problem lies elsewhere, e.g. an unstable backend, no tests or chaotic architecture. A new UI on bad foundations is the same problem in nicer packaging. In that situation, start by assessing your technical debt.

Gradual migration: old and new code side by side

The most important point for a product owner: you don't have to migrate everything at once. Both frameworks were designed so that old and new code can coexist in a single app:

  • on Android, a Compose component can be embedded in an existing XML screen (ComposeView), and an old view can be placed inside a Compose screen (AndroidView);
  • on iOS, a SwiftUI screen is added to a UIKit app via UIHostingController, and existing UIKit components are used inside SwiftUI via UIViewRepresentable.

This means the migration can run in parallel with product development. Users can't tell which screen uses which technology, and the team doesn't have to freeze new features. It is the same idea we described when discussing whether to refactor or rewrite from scratch: gradually replacing old code with new instead of one big leap.

Step-by-step migration plan

1. Foundations: theme and base components

Before the first new screen is built, you need to move the design system: colours, typography, spacing and the most frequently used components (buttons, form fields, cards, headers). Without this, every migrated screen ends up slightly different, and a year later the app looks as if it had been stitched together from two projects. This is the step most often skipped, and the one most often regretted.

2. Screenshot tests before making changes

A UI migration is a change that business logic tests won't catch. Before you start rewriting a screen, set up screenshot comparison tests (on Android e.g. Paparazzi or Roborazzi, on iOS a snapshot testing library). After the migration you compare the before and after images, so a spacing difference of a few pixels shows up immediately rather than in feedback from the client.

3. New screens in the new technology from day one

From day zero, every new screen is built in Compose or SwiftUI. This is the cheapest part of the migration because nothing needs to be rewritten, and it shows the team most quickly whether the new approach really speeds up their work.

4. Existing screens as you go

Existing screens are migrated when they need to change anyway: for a new feature, a bug fix or a visual change. The extra cost of migration is then spread across normal work and is much lower than in a separate project. It is worth setting the order using two criteria: how often a screen changes and how problematic it is.

5. Navigation last

Navigation between screens is the backbone of the app. Switching it to the new approach only makes sense once most screens have already been migrated. Doing it too early means constantly jumping between the two worlds and writing a lot of glue code.

Pitfalls worth knowing about

  • Minimum OS version on iOS. What SwiftUI can do depends heavily on the iOS version: modern navigation requires iOS 16, and the new data observation model requires iOS 17. If the app has to run on iOS 15 or older, the migration is harder and less worthwhile. Check your analytics to see how many users actually have phones that old; it is often a fraction of a percent.
  • Performance of long lists. Poorly written lists in Compose and SwiftUI can redraw unnecessarily. This is not a flaw in the framework but a lack of experience, and it shows up as stuttering while scrolling, which users notice immediately.
  • Two styles in one project forever. The worst-case scenario is a migration started with enthusiasm and abandoned halfway. That is why you need a plan with the order of screens and a decision on what counts as "migrated enough". Not every screen has to be rewritten: a rarely used settings screen can safely stay on the old technology.
  • Updating the rest of the project. Compose and newer versions of SwiftUI require up-to-date tooling: Kotlin, Gradle, Xcode. If the project has stood still for years, the first step of the migration is updating the environment, and that is often the least predictable part.

How much it costs

The cost depends on the number of screens, their complexity and the state of the project, so treat the ranges below as indicative, a starting point for a conversation rather than a quote:

Item Indicative effort Updating tools and libraries (if the project has been dormant for a while) from a few days to 2–3 weeks Foundations: theme, base components, screenshot tests 1–3 weeks per platform Simple screen (list, details, form) 0.5–2 days Complex screen (many states, animations, custom components) 3–8 days Navigation 1–3 weeks, depending on the number of flows and deep links

For an app with 30–40 screens, a full migration of one platform is typically two to four months of work for one person. If you migrate as you go, a large part of that cost would have been spent on modifying screens anyway, so the real additional spend is noticeably lower than the table total. If you don't know what state the project is in, a sensible first step is a short code audit, which will tell you whether migration is the right place to start.

Summary

Migrating to Jetpack Compose and SwiftUI is a modernization that pays for itself when the app has years of development ahead of it. It is best done gradually: foundations and screenshot tests first, then new screens in the new technology, existing screens whenever they change, and navigation last. That way the product keeps evolving without interruption and the cost of the migration is spread across regular work.

We handle app modernization, from the audit and migration plan to carrying it out, as part of mobile app maintenance or as support for your team when you have your own developers and need migration experience. If you want to find out where to start in your project, get in touch or book a consultation.