Type-Safe Routes

Type-Safe Navigation with Kotlin Serializable Routes

Since Navigation 2.8.0, a route is a Kotlin type. An object is a destination without arguments; a data class is one with arguments, and its properties are the arguments:

ui/nav/Routes.kt: BookNest's destinationsKotlin
@Serializable object ShopGraph                       // a nested graph: catalog and detail
@Serializable object Catalog
@Serializable data class BookDetail(val id: Int)     // an argument is just a property
@Serializable object Cart
@Serializable object About

@Serializable needs the kotlinx.serialization 5,947 compiler plugin, org.jetbrains.kotlin.plugin.serialization, at the same version as Kotlin (2.4.20 here); navigation-compose brings the runtime library with it. The graph then uses the types as keys, and a destination reads its arguments back as the same type:

ui/nav/BookNestNav.kt: navigating to and reading a typed routeKotlin
          composable<BookDetail>(
            deepLinks = listOf(navDeepLink<BookDetail>(basePath = "booknest://book")),
          ) { entry ->
            val id = entry.toRoute<BookDetail>().id             // typed, no Bundle keys
            val book = books.first { it.id == id }

nav.navigate(BookDetail(book.id)) no longer compiles with a string or a missing ID. Under the hood nothing changed: the plugin generates a serializer, and Navigation turns the class into the same kind of URL-like route it always used. The log in NavController and NavHost shows it, com.example.booknest.ui.nav.BookDetail/{id}, the class's full name plus a path argument per property; nullable or defaulted properties become query parameters. That is also why arguments must be simple (numbers, strings, enums, or a custom NavType). In a ViewModel, savedStateHandle.toRoute<BookDetail>() reads the same arguments (ViewModel and UDF).