Navigation Component
The previous lesson ended with raw FragmentTransaction calls — functional, but
entirely manual, and easy to get subtly wrong once a screen has more than one or two paths in and
out of it. The Navigation Component is Jetpack's answer: a library that manages
moving between Fragments (and Activities) for you, so you stop hand-writing transactions and
back-stack bookkeeping yourself.
The Navigation Graph
At the center of the library is the Navigation Graph — an XML resource, usually
named nav_graph.xml, that acts as a visual map of every screen in your app (each one
called a destination) and the possible paths between them, called
actions. For TrailLog, a minimal graph has exactly two destinations:
trailListFragment
Shows every logged trail.
trailDetailFragment
Shows one trail's full details.
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/trail_nav_graph"
app:startDestination="@id/trailListFragment">
<fragment
android:id="@+id/trailListFragment"
android:name="com.codingwithsonu.traillog.TrailListFragment">
<action
android:id="@+id/action_trailList_to_trailDetail"
app:destination="@id/trailDetailFragment" />
</fragment>
<fragment
android:id="@+id/trailDetailFragment"
android:name="com.codingwithsonu.traillog.TrailDetailFragment" />
</navigation>
NavHostFragment: Where Destinations Get Swapped In
The graph describes the map, but something still has to actually display each destination inside
an Activity. That's NavHostFragment — placed once in an Activity's layout, it's the
container the Navigation Component swaps Fragments inside of as the user moves around, entirely
replacing the container you'd otherwise wire up by hand with a FragmentContainerView.
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/trail_nav_graph"
app:defaultNavHost="true" />
Safe Args: Type-Safe Navigation Instead of Bundle Keys
Passing data through the Navigation Component with raw Bundle string keys works, but
it means a typo in a key name only surfaces as a crash at runtime. The Navigation Safe
Args Gradle plugin fixes this by generating a small, strongly-typed class for every
action in your graph, based on any arguments you declare on the destination — giving you
compile-time-checked navigation instead of stringly-typed Bundle lookups. This is a genuinely nice
improvement over the plain Bundle approach, and it feels distinctly Kotlin-friendly once you see
it in use.
Worked Example: Passing a Trail's Id with Safe Args
First, declare the argument on the destination that receives it:
<fragment
android:id="@+id/trailDetailFragment"
android:name="com.codingwithsonu.traillog.TrailDetailFragment">
<argument
android:name="trailId"
app:argType="integer" />
</fragment>
Safe Args then generates a matching Directions class from the action you defined
earlier, which the list screen uses to navigate with the argument attached:
// inside TrailListFragment, e.g. when a list item is tapped
private fun onTrailClicked(trail: Trail) {
val action = TrailListFragmentDirections
.actionTrailListToTrailDetail(trailId = trail.id)
findNavController().navigate(action)
}
And on the receiving end, TrailDetailFragment reads the argument back through a
generated Args class rather than a raw Bundle lookup:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val args: TrailDetailFragmentArgs by navArgs()
val trailId = args.trailId
// look up and render the Trail matching trailId
}
If trailId were ever renamed in the graph, this code simply fails to compile until
it's updated — exactly the kind of mistake you want caught long before the app ships.
The Back Stack: popBackStack() and navigateUp()
The Navigation Component maintains a back stack automatically as the user moves between
destinations, so pressing the system back button already does the sensible thing by default. When
you need to control it yourself — say, dismissing the detail screen after a save completes —
you call findNavController().popBackStack() to return to the previous destination, or
findNavController().navigateUp(), which behaves like the toolbar's up button and
respects any parent/child relationship declared in the graph.