Navigation Compose Ditches String Routes for Compile-Time Safety
String-based routing has been a persistent source of runtime crashes and silent breakage in Android navigation. Compile-time type safety eliminates an entire class of bugs and makes refactoring safe, while the callback-hoisting pattern keeps Compose screens decoupled enough to test without a real navigation host.
Navigation in Compose moves from launching components to defining a graph of typed destinations. A sealed class hierarchy annotated with `@Serializable` replaces fragile string-based routes, so `navController.navigate(Screen.Profile(id))` carries no risk of typos or broken argument parsing. The `toRoute()` extension pulls parameters directly from the back stack entry without manual deserialization.
Architecturally, navigation logic stays out of UI composables through callback hoisting: screens expose lambdas like `onUserClick: (String) -> Unit`, and a top-level coordinator wires them to `navController` calls. This keeps composables previewable and testable in isolation. For cross-screen data, the guidance is to pass only minimal IDs — a `userId` rather than a full object — to avoid transaction size limits and let each screen's ViewModel fetch its own fresh state.
Pitfalls include never singletons a `NavController` outside composition context, using effect streams from ViewModels to trigger navigation, and splitting large graphs into nested sub-graphs for modular apps. Deep linking works directly on the type-safe destinations.
Compile-time route checking is a genuine safety upgrade over string routes, but the real architectural win is the callback-hoisting pattern — it forces a clean separation that many teams skip when `navController` is too easy to pass around.
The advice to pass only IDs rather than objects is old wisdom, yet Compose's stateless recomposition model makes it more urgent: a recreated screen with stale parceled data looks correct but isn't.
Type-safe deep links close a gap that often pushed teams back toward manual intent parsing; having them declared in the same sealed class hierarchy keeps all entry points visible in one place.
Singleton `NavController` outside composition is a common mistake that survives code review because it appears to work until a configuration change or process death reveals the broken state binding.
The discussion pivots entirely to Navigation Compose 3 (Nav3), questioning the article's focus on Nav2. A defense of Nav2 rests on production stability and respect for legacy systems, arguing against reckless adoption of untested frameworks. A counterpoint champions Nav3's state-driven, list-based architecture as a more idiomatic Compose solution that offers finer control and serializable type safety.