Migrating 170 Screens to Android Navigation 3: Overtime, Crashes, and Bottom-Sheet Surprises
Navigation 3 is Google’s forward path for Compose-first, adaptive Android apps, but migrating a large production app is a one-shot, high-risk operation with no incremental escape hatch. The serialization contract is strict and easy to break in ways that only surface in production, and bottom sheets demand explicit lifecycle management that the framework doesn’t enforce.
A single developer rewrote an entire 170-screen Android app’s navigation layer from Navigation 2 to Navigation 3 over two weeks of overtime, while the rest of the team kept shipping features on the old stack. The migration was all-or-nothing: incremental adoption is impossible because Nav2 and Nav3 share no backward compatibility. Bottom sheets became the biggest unplanned headache—Nav3 ships no built-in bottom-sheet support, forcing a custom metadata wrapper around entries that later got replaced by Google’s SceneStrategy API.
The first production rollout to 1% of users immediately triggered `SerializationException` crashes. Passing a non-serializable `NavKey` as a navigation argument broke at runtime; the fix was a thin `@Serializable` wrapper class. A month and a half later, Google released a Claude Code skill purpose-built for Nav3 migrations. Retroactively testing it on the pre-migration branch showed it would have cut the work roughly in half, though legacy extensions and custom navigation logic still required manual intervention.
Deep links got a quiet overhaul during the migration—centralizing previously scattered logic into a single place with type-safe, serializable route definitions. The payoff is faster debugging and simpler onboarding for new screens. The team now treats adaptive layouts and multi-pane UIs as a configuration detail rather than a separate project.
Nav3’s all-or-nothing migration model creates a perverse incentive to rush: the longer you take, the more merge debt you accumulate against a moving codebase.
Bottom sheets are the sharpest edge in Nav3—no framework guardrails, no compile-time safety, and a runtime behavior that silently breaks the back stack if you forget one dismiss call.
Serialization is the new runtime contract for navigation arguments, and it fails silently until a user hits the exact flow that passes a non-serializable key.
Google’s own AI tooling for Nav3 migration arrived too late for early adopters, which means the first wave of teams paid a tax that later teams can largely skip.
Deep links are often an afterthought in navigation design, but migrating them into a centralized, type-safe structure turned out to be one of the highest-ROI changes.
Automated testing surfaced navigation bugs that manual QA would have missed entirely, reinforcing that navigation state is just another state machine that needs deterministic verification.
The migration effectively deleted a large chunk of legacy navigation extension functions—code that existed only to patch gaps in Nav2’s API surface.