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.

──action──▶

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.

Note: All XML and Kotlin here is correct, static example code — this website's own project doesn't run a live Android Studio build, so it isn't executed on this page.
Next: Now that TrailLog can navigate between screens cleanly, ViewModel & LiveData covers how to hold and observe the data those screens actually display, in a way that survives configuration changes.