As of Navigation 2.8.0-alpha08, the Navigation Part has a full kind protected system primarily based on Kotlin Serialization for outlining your navigation graph when utilizing our Kotlin DSL, designed to work greatest with integrations like Navigation Compose.
The Navigation Part has three foremost elements:
- Host — the UI component in your format that shows the present ‘vacation spot’
- Graph — the information construction that defines the entire attainable locations in your app
- Controller — the central coordinator that manages navigating between locations and saving the again stack of locations
The Kotlin DSL is simply one of many methods to construct that navigation graph. Since the very first alpha of Navigation again in 2018, Navigation has at all times supplied 3 ways to construct the graph:
- Manually setting up cases of
NavGraph
and including locations like Fragment Locations to assemble the graph (that is nonetheless the underlying base for every thing else, however not one thing you must actively be doing your self) - Inflating your graph from a navigation XML file, enhancing it by hand or by way of the Navigation Editor
- Utilizing the Kotlin DSL to assemble your navigation graph immediately in your Kotlin code
Navigation Compose was the primary integration to actually embrace the Kotlin DSL as the method to construct your graph, purposefully transferring to a extra versatile system and away from static XML recordsdata.
Nevertheless, the transfer from construct time static XML recordsdata to producing your graph at runtime meant that the instruments obtainable to builders additionally modified considerably. The Navigation Part’s Protected Args Gradle Plugin, which generated kind protected “Instructions” courses you would use in your code to navigate between locations, relied on studying the locations and their arguments from these navigation XML recordsdata. Meaning with out navigation XML recordsdata, there was no generated Protected Args code.
And whereas Navigation requires the right sorts and arguments at runtime (telling you loudly (crashing) in case you tried to cross a String to one thing anticipating an Int or in case you forgot a required argument), compile time security was left as an train to the developer.
With out the Protected Args Gradle plugin, what would you’ve gotten left? The Kotlin DSL with Navigation Compose was primarily based on the concept every vacation spot had a novel “route” — a RESTful path that uniquely identifies that vacation spot.
For instance, similar to an internet site, you might need a "residence"
vacation spot, a "merchandise"
vacation spot, in addition to pages that take arguments — the route for a selected product is perhaps "merchandise/{productId}"
— it could embody a placeholder for the distinctive ID of that product.
That meant:
- Preserving monitor of those string routes
- Managing their arguments and their sorts
- Worst of all, doing string interpolation
Our personal documentation and video content material explored easy methods to reduce this bookkeeping — isolating the strings alongside kind protected extensions on prime of our base Kotlin DSL. Whereas this supplied a robust barrier between the bottom Kotlin DSL and the API you expose throughout the remainder of your code base and throughout completely different modules, it clearly wasn’t sufficient.
I’d prefer to personally thank the bigger Android group for offering some incredible options constructed on prime of Navigation Compose designed to reduce or fully remove this manually written code together with:
Rafael Costa’s Compose Locations makes use of KSP to course of annotations hooked up to composable features to generate the complete navigation graph.
Kiwi.com’s navigation-compose-typed makes use of Kotlin Serialization to generate routes immediately from @Serializable
objects or information courses.
When taking a look at what ‘Protected Args’ would appear to be in our Kotlin DSL, we explored numerous approaches, basically taking a look at most of the applied sciences obtainable to us to generate kind protected code.
One method we explored was a transliteration of what we did with the Protected Args Gradle Plugin — quite than a Gradle plugin that will learn the supply of reality (your navigation XML file), we’d use your current Kotlin code because the supply of reality.
That meant in case you wrote a bit of your Kotlin DSL that seemed like:
composable(
route = "profile/{userId}/{title}",
arguments = listOf(
navArgument("userId") {
kind = NavType.Int,
nullable = false
},
navArgument("title") {
kind = NavType.String,
nullable = true
}
)
) {
We’d generate the ProfileDestination
and ProfileArgs
courses you’d have to navigate to the "profile"
vacation spot and extract these arguments out. After trying into this….this was method simpler stated than performed. Info just like the string "profile/{userId}/{title}"
was technically attainable to extract, however solely as a Kotlin Compiler Plugin. And even then, whereas we may discover the route String handed to the Kotlin DSL, it was tough to resolve the String if it was something aside from a continuing String. On condition that we have now numerous Kotlin Compiler Plugin specialists on our bigger staff who know precisely how a lot upkeep is concerned in a compiler plugin (hello Compose people!) and that we’re at present transitioning between the K1 and K2 compilers, we selected to not develop this answer any additional.
So if the Kotlin DSL code wasn’t a viable supply of reality, what could possibly be a viable supply of reality? And what instruments had been obtainable to learn that data? It seems the opposite two massive choices (KSP and Kotlin Serialization) are additionally Kotlin Compiler Plugins. However importantly: they’re ones that ship alongside each model of Kotlin, which is vital for getting out of the way in which of builders keen to make use of new variations of Kotlin as they arrive out.
One of many guiding rules we’ve adopted in creating the Navigation Part is in making an attempt to reduce how ‘infectious’ Navigation code is: e.g., how simple is it to swap out our library for one more (no judgment!). If in case you have Navigation code and courses unfold all through your complete code base in each file, you’re by no means going to eliminate it.
That’s why our testing information particularly recommends avoiding having any references to your NavController in your display degree composable strategies and particularly why there isn’t a NavController composition native: a button deep in your hierarchy, as handy as it could be, shouldn’t be tied to your explicit alternative of navigation library.
So when in search of a ‘supply of reality’ for easy methods to outline every vacation spot in our graph, having every of these definitions completely unbiased of Navigation’s courses was precisely the kind of method we had been in search of.
This meant that in case you needed to outline a brand new vacation spot in your navigation graph, you would write the best code attainable:
// Outline a house vacation spot that does not take any arguments
@Serializable
object House// Outline a profile vacation spot that takes an ID
@Serializable
information class Profile(val id: String)
You’ll be aware that these purposefully don’t have to implement any Navigation supplied interface and even be outlined in a module that has a Navigation dependency. But, they’re sufficient to encapsulate a significant title of the vacation spot (I believe you may get some appears in case you named it object Object1
) and any parameters which might be core to the id of that vacation spot. That appears prefer it could possibly be a viable supply of reality.
With Kotlin Serialization as a viable supply for compile time security, we proceeded to take each API that took a String route and add Kotlin Serialization primarily based overloads.
So having outlined your House
and Profile
Serializable courses, your graph now appears like:
NavHost(navController, startDestination = House) {
composable {
HomeScreen(onNavigateToProfile = { id ->
navController.navigate(Profile(id))
})
}
composable { backStackEntry ->
val profile: Profile = backStackEntry.toRoute()
ProfileScreen(profile)
}
}
You need to be aware one factor instantly: no strings! Particularly:
- No route string when defining a composable vacation spot — specifying the sort is sufficient to generate the route for you in addition to the arguments (no extra
navArgument
both) - No route string when navigating to a brand new vacation spot. You cross NavController the
Serializable
object related to the vacation spot you wish to navigate to. - No route string when defining the beginning vacation spot of a navigation graph.
For the Profile
display, we use the toRoute()
extension technique to recreate the Profile
object from the NavBackStackEntry
and its arguments. There’s the same extension technique on SavedStateHandle
, making it simply as simple to get the sort protected arguments in your ViewModel as effectively while not having to reference particular argument keys.
This new method applies to particular person locations, so you’ll be able to incrementally migrate out of your present method to this new method, one vacation spot or one module at a time.
One in every of my private favourite options of this sort protected method is in making it very clear which APIs help a route sample (e.g., "profile/{id}"
) and which help a stuffed in route (e.g., "profile/42"
). As an illustration, the popBackStack()
API really helps each, however that wasn’t clear when its parameter was only a String. With the sort protected APIs, it’s a lot clearer:
// Pop as much as the topmost occasion of the Profile display, inclusive
navController.popBackStack(inclusive = true)// Pop as much as the precise occasion of the Profile display with ID 42
// additionally popping another cases which might be on prime of it within the again stack
navController.popBackStack(Profile(42), inclusive = true)
So once you see an API that takes a reified class, you’ll know that it denotes any vacation spot of that kind, no matter its arguments. Whereas one which takes an precise occasion of that class is used to discover a particular vacation spot with precisely these matching arguments.
APIs like getBackStackEntry()
and even the startDestination
of your graph are examples the place technically they’ve supported each for a while and also you may not have even recognized it!
When you’re actually doing one thing customized past the primitive sorts (or their Array and now Record equivalents), difficult sorts like Parcelable
sorts may even be used as fields in your Serializable
courses by writing your individual customized NavType and passing it by way of when constructing your graph:
// The Search display requires extra difficult parameters
@Parcelable
information class SearchParameters(
val searchQuery: String,
val filters: Record
)@Serializable
information class Search(
val parameters: SearchParameters
)
val SearchParametersType = object : NavType(
isNullableAllowed = false
) {
// See the customized NavType docs linked above for an
//instance of easy methods to implement this
}
// Now use this in your vacation spot
composable(
typeMap = mapOf(typeOf() to SearchParametersType)
) { backStackEntry ->
val searchParameters = backStackEntry.toRoute().parameters
}
Word: that is supposed to be a pace bump: suppose lengthy and exhausting whether or not an immutable, snapshot-in-time argument is basically the supply of reality for this information, or if this could actually be an object you retrieve from a reactive supply, comparable to a Circulate uncovered from a repository that will routinely refresh in case your information modifications.
The whole kind protected API is out there beginning in Navigation 2.8.0-alpha08. Apart from help for the entire Kotlin DSL builders we help (together with each Navigation Compose that we talked about right here and Navigation with Fragments), it additionally consists of different APIs you may discover attention-grabbing just like the navDeepLink
API that takes a Serializable
class and a prefix that means that you can simply join exterior hyperlinks to the identical kind protected APIs.
When you discover any points or have function requests for APIs we missed, please file a problem — whereas these APIs are nonetheless in alpha is the very best time to request modifications.