I held onto the old XML view system longer than most. Here’s what finally moved me to Jetpack Compose for good.

State-driven UI just makes more sense. Describing what the UI should look like for a given state, instead of manually mutating views, eliminates an entire category of “the UI is out of sync with the data” bugs.

Less code for the same result. A screen that took 150 lines of XML plus a matching Java/Kotlin adapter now takes a fraction of that in Compose, which means less surface area for bugs to hide in.

Previews speed up iteration. Seeing a component render instantly without deploying to a device or emulator changes how fast you can try five versions of a layout.

The ecosystem caught up. Early on, missing library support was a real blocker. That’s mostly gone now, most major libraries have first-class Compose support.

There’s still a learning curve if you’re coming from the old view system, but it’s a short one compared to the time it saves afterward.

What the migration actually took

I didn’t rewrite everything at once. Compose interops with the existing View system, so new screens got built in Compose while older, stable screens stayed as XML until they needed real changes anyway. That incremental path is exactly what Android’s own migration guidance recommends, and it avoided a risky big-bang rewrite.

Recomposition performance needs to be learned deliberately. Compose’s biggest early pitfall isn’t syntax, it’s accidentally causing more of the UI tree to recompose than necessary. Learning to read the layout inspector’s recomposition counts early saved a lot of later debugging.

Testing changed for the better. Compose’s testing APIs let you assert on semantics directly rather than fighting with View IDs and Espresso matchers, which made UI tests noticeably less brittle across refactors.

Design handoff got smoother too. Because Compose UI so closely mirrors a declarative description of the screen, translating a design file into code involves less interpretation than the old imperative View-building approach did.

The switch paid for itself within the first couple of features. If you’re earlier in the decision, weighing Compose-based native Android against a cross-platform framework entirely, I cover that broader tradeoff in Choosing Between Native, Flutter, and React Native.

A recomposition bug that taught me the biggest lesson

Early in the migration, a list screen felt sluggish in a way the old XML version never had. The layout inspector showed the entire list recomposing on every single character typed into an unrelated search field above it, because both lived inside the same composable function and shared state at too broad a scope. Splitting the search field and the list into separate composables, each observing only the state it actually needed, fixed the jank instantly and taught me the core Compose performance lesson: scope your state as narrowly as possible, or the framework will happily recompute far more than necessary.

That single bug shaped how I structure every Compose screen since. Small, focused composables that each own the minimum state they need aren’t just cleaner code, they’re a direct performance decision, not just a style preference.

It’s the kind of lesson that’s hard to learn from documentation alone and much easier to learn from watching a real screen stutter and tracing it back to a scoping mistake. Compose rewards that kind of debugging early, because the tools for seeing exactly what recomposed and why are genuinely good once you know to look.

What I would tell someone starting fresh today

Anyone starting a new Android project today has an easier path than the one I had migrating an existing codebase, there is no legacy XML to interoperate with, no gradual conversion strategy to plan, just a single, well-documented starting point in Compose from the very first screen. The learning curve that felt steep during migration is considerably gentler when there is no old mental model to unlearn at the same time as the new one is being learned, which is worth knowing if the size of this decision feels intimidating from the outside.

The single best use of a first week with Compose is building the same small screen two or three different ways on purpose, not to ship any of them, but to feel the tradeoffs directly. Building a simple list screen with state hoisted at the top level, then again with state scoped too broadly, then again scoped correctly, makes the recomposition lessons land in a way that reading about them never quite achieves. Compose rewards that kind of deliberate, throwaway experimentation early far more than most frameworks do, because the preview and inspection tooling make the feedback loop for trying something and seeing the result almost instant.

The switch is not just a technical upgrade, it changes how a team thinks about UI altogether, from imperative instructions about how to mutate a view to a declarative description of what the screen should look like for a given state. That shift in thinking is the real payoff, and it is worth the short-term friction of getting there.

If your team is still early in evaluating Compose, the honest advice is to stop evaluating and start building a small, real screen with it instead. The framework rewards hands-on experimentation far more than it rewards research from the sidelines.

The short-term friction of learning something new is almost always smaller in hindsight than it felt going in, and this migration was no exception.

Compose is not just a new API to memorize, it is a genuinely different way of thinking about how a screen comes to exist on a device. Teams that give themselves permission to think in that new way, rather than translating old View-based habits line by line, tend to reach real fluency far faster than teams trying to map every old pattern onto a new syntax.

The payoff compounds with every new screen built the right way from the start.

Six months from now, the version of this decision that looks obvious in hindsight is the one made today, and teams that start the transition early get to make that call on their own timeline instead of a forced one later.