The Navigation component provides a Kotlin-based domain-specific language, or
DSL, that relies on Kotlin's
type-safe builders.
This API allows you to declaratively compose your graph in your Kotlin code,
rather than inside an XML resource. This can be useful if you wish to build
your app's navigation dynamically. For example, your app could download and
cache a navigation configuration from an external web service and then use
that configuration to dynamically build a navigation graph in your activity's
onCreate()
function.
Dependencies
To use the Kotlin DSL, add the following dependency to your app's
build.gradle
file:
dependencies { def nav_version = "2.3.2" api "androidx.navigation:navigation-fragment-ktx:$nav_version" }
Building a graph
Let's start with a basic example based on the
Sunflower app. For this
example, we have two destinations: home
and plant_detail
. The home
destination is present when the user first launches the app. This destination
displays a list of plants from the user's garden. When the user selects one of
the plants, the app navigates to the plant_detail
destination.
Figure 1 shows these destinations along with the arguments required by the
plant_detail
destination and an action, to_plant_detail
, that the app uses
to navigate from home
to plant_detail
.

home
and plant_detail
, along with an action that
connects them.Create the host for your Kotlin DSL nav graph
Regardless of how you build your graph, you need to host the graph in a
NavHost
.
Sunflower uses fragments, so let's use a
NavHostFragment
inside of a
FragmentContainerView
,
as shown in the following example:
<!-- activity_garden.xml -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true" />
</FrameLayout>
Notice that the app:navGraph
attribute is not set in this example, since the
graph is built programmatically instead of being defined as an
XML resource.
Create constants for your graph
When working with
XML-based navigation graphs,
the Android build process parses your graph resource file and defines numeric
constants for each id
attribute defined in the graph. These constants are
accessible in your code through a generated resource class,
R.id
.
For example, the following XML graph snippet declares a fragment destination
with an id
, home
:
<navigation ...>
<fragment android:id="@+id/home" ... />
...
</navigation>
The build process creates a constant value, R.id.home
, that is associated with
this destination. You can then reference this destination from your code by
using this constant value.
This process of parsing and generating constants does not occur when you build
a graph programmatically using the Kotlin DSL. Instead, you must define your
own constants for each destination, action, and argument that has
an id
value. Each ID must be unique and consistent across configuration
changes.
One organized way to create constants is to create a nested set of Kotlin
object
s that define the constants statically, as shown in the following
example:
object nav_graph {
const val id = 1 // graph id
object dest {
const val home = 2
const val plant_detail = 3
}
object action {
const val to_plant_detail = 4
}
object args {
const val plant_id = "plantId"
}
}
With this structure, you can access the ID values in code by chaining the object calls together, as shown in the following examples:
nav_graph.id // graph id
nav_graph.dest.home // home destination id
nav_graph.action.to_plant_detail // action home -> plant_detail id
nav_graph.args.plant_id // destination argument name
Build a graph with the NavGraphBuilder DSL
Once you've defined your initial set of IDs, you can build the navigation
graph. Use the
NavController.createGraph()
extension function to create a NavGraph
,
passing in an id
for your graph, an ID value for the startDestination
,
and a trailing lambda that defines the structure of your graph.
You can build your graph in your activity's onCreate()
function.
createGraph()
returns a
Navgraph
that you can then assign to the graph
property of the
NavController
that is
associated with your NavHost
, as
shown in the following example:
class GardenActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_garden)
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host) as NavHostFragment
navHostFragment.navController.apply {
graph = createGraph(nav_graph.id, nav_graph.dest.home) {
fragment<HomeViewPagerFragment>(nav_graph.dest.home) {
label = getString(R.string.home_title)
action(nav_graph.action.to_plant_detail) {
destinationId = nav_graph.dest.plant_detail
}
}
fragment<PlantDetailFragment>(nav_graph.dest.plant_detail) {
label = getString(R.string.plant_detail_title)
argument(nav_graph.args.plant_id) {
type = NavType.StringType
}
}
}
}
}
}
In this example, the trailing lambda defines two fragment destinations using the
fragment()
DSL builder function. This function requires an ID for the destination. The
function also accepts an optional lambda for additional configuration, such as
the destination label
, as well as embedded builder functions for
actions, arguments, and deep links.
The Fragment
class that
manages the UI of each destination is passed in as a parameterized type inside
angle brackets (<>
). This has the same effect as setting the android:name
attribute on fragment destinations that are defined using XML.
Navigating with your Kotlin DSL graph
Once you've built and set your graph, you can then navigate from home
to
plant_detail
using NavController.navigate()
, as shown in the following
example:
private fun navigateToPlant(plantId: String) {
val args = bundleOf(nav_graph.args.plant_id to plantId)
findNavController().navigate(nav_graph.action.to_plant_detail, args)
}
Supported destination types
The Kotlin DSL supports Fragment
, Activity
, and NavGraph
destinations,
each of which has its own inline extension function available for building
and configuring the destination.
Fragment destinations
The fragment()
DSL function can be parameterized to the implementing Fragment
class. This
function takes a unique ID to assign to this destination along with a lambda
where you can provide additional configuration.
fragment<FragmentDestination>(nav_graph.dest.fragment_dest_id) {
label = getString(R.string.fragment_title)
// arguments, actions, deepLinks...
}
Activity destination
The activity()
DSL function takes a unique ID to assign to this destination but is not
parameterized to any implementing activity class. Instead, you can set an
optional activityClass
in a trailing lambda. This flexibility allows you to
define an activity destination for an activity that is launched from an
implicit intent, where an
explicit activity class would not make sense. As with fragment destinations,
you can also define and configure a label and any arguments.
activity(nav_graph.dest.activity_dest_id) {
label = getString(R.string.activity_title)
// arguments, actions, deepLinks...
activityClass = ActivityDestination::class
}
Navigation graph destination
You can use the navigation()
DSL function to build a
nested navigation graph.
As with the other destination types, this DSL function takes three arguments:
an ID to assign to the graph, a starting destination ID for the graph, and a
lambda to further configure the graph. Valid elements for the lambda include
arguments, actions, other destinations, deep links, and a label.
navigation(nav_graph.dest.nav_graph_dest, nav_graph.dest.start_dest) {
// label, arguments, actions, other destinations, deep links
}
Supporting custom destinations
You can use
addDestination()
to add custom destination types to your Kotlin DSL that are not directly
supported by default, as shown in the following example:
// The NavigatorProvider is retrieved from the NavController
val customDestination = navigatorProvider[CustomNavigator::class].createDestination().apply {
id = nav_graph.dest.custom_dest_id
}
addDestination(customDestination)
You can also use the unary plus operator (+
) to add a newly-constructed
destination directly to the graph:
// The NavigatorProvider is retrieved from the NavController
+navigatorProvider[CustomNavigator::class].createDestination().apply {
id = nav_graph.dest.custom_dest_id
}
Providing destination arguments
You can define optional or required arguments for any destination type. To
define an argument, call the
argument()
function on NavDestinationBuilder
, the base class for all destination builder
types. This function takes the argument's name as a String
and a lambda that
you can use to construct and configure a
NavArgument
.
Inside the lambda, you can specify the argument data type, a default value if
applicable, and whether the argument value can be null
.
fragment<PlantDetailFragment>(nav_graph.dest.plant_detail) {
label = getString(R.string.plant_details_title)
argument(nav_graph.args.plant_name) {
type = NavType.StringType
defaultValue = getString(R.string.default_plant_name)
nullable = true // default false
}
}
If a defaultValue
is given, type
is optional. In this case, if no type
is specified the type is inferred from defaultValue
. If both a
defaultValue
and type
are provided, the types must match. For a complete
list of argument types, see
NavType
.
Actions
You can define actions within any destination, including
global actions within
the root navigation graph. To define an action, use the
NavDestinationBuilder.action()
function, supplying an ID to the function and a lambda to provide additional configuration.
The following example builds an action with a destinationId
, transition
animations, and pop and single-top behavior.
action(nav_graph.action.to_plant_detail) {
destinationId = nav_graph.dest.plant_detail
navOptions {
anim {
enter = R.anim.nav_default_enter_anim
exit = R.anim.nav_default_exit_anim
popEnter = R.anim.nav_default_pop_enter_anim
popExit = R.anim.nav_default_pop_exit_anim
}
popUpTo(nav_graph.dest.start_dest) {
inclusive = true // default false
}
// if popping exclusively, you can specify popUpTo as
// a property. e.g. popUpTo = nav_graph.dest.start_dest
launchSingleTop = true // default false
}
}
Deep links
You can add deep links to any destination, just as with an XML-based navigation graph. The same procedures defined in Creating a deep link for a destination apply to the process of creating an explicit deep link using the Kotlin DSL.
When creating an implicit deep link, however, you don't have an XML navigation
resource that can be analyzed for <deepLink>
elements. Therefore, you can't
rely on placing a <nav-graph>
element in your AndroidManifest.xml
file and
must instead manually add intent filters
to your activity. The intent filter you supply should match the base URL
pattern of your app's deep links.
For each individually-deep-linked destination, you can supply a more specific
URI pattern using the
deepLink()
DSL function. This function accepts a String
for the URI pattern, as shown
in the following example:
deepLink("http://www.example.com/plants/")
There is no limit to the number of deep link URIs you can add. Each call to
deepLink()
appends a new deep link to an internal list specific to that
destination.
Here's a more complex implicit deep link scenario that also defines path- and query-based parameters:
val baseUri = "http://www.example.com/plants"
fragment<PlantDetailFragment>(nav_graph.dest.plant_detail) {
label = getString(R.string.plant_details_title)
deepLink("${baseUri}/{id}")
deepLink("${baseUri}/{id}?name={plant_name}")
argument(nav_graph.args.plant_id) {
type = NavType.IntType
}
argument(nav_graph.args.plant_name) {
type = NavType.StringType
nullable = true
}
}
Note that string interpolation can be used to simplify the definition.
Creating IDs
The Navigation library requires that the ID values used for graph elements be unique integers that remain constant through configuration changes. One way to create these IDs is to define them as static constants as shown in Create constants for your graph. You can also define static resource IDs in XML as a resource. Alternatively, you can dynamically construct IDs. For example, you could create a sequence counter that is incremented each time you reference it.
object nav_graph {
// Counter for id's. First ID will be 1.
var id_counter = 1
val id = id_counter++
object dest {
val home = id_counter++
val plant_detail = id_counter++
}
object action {
val to_plant_detail = id_counter++
}
object args {
const val plant_id = "plantId"
}
}
Limitations
- The Safe Args plugin is incompatible with the Kotlin DSL, as the plugin
looks for XML resource files to generate
Directions
andArguments
classes.