Skip to main content

Kotlin Navigation Component

Introduction

The Navigation Component is a part of Android Jetpack that helps you implement navigation in your Android applications written in Kotlin. It provides a framework to navigate between different destinations within your app with proper handling of back stack, arguments, animations, and deep links.

Navigation Component simplifies the implementation of common navigation patterns such as drawers, bottom navigation, and up/back actions. It also provides a visual editor in Android Studio that allows you to see and edit your navigation flow.

In this tutorial, we'll explore how to set up and use the Navigation Component in your Kotlin Android applications.

Prerequisites

Before diving into the Navigation Component, you should have:

  • Android Studio installed
  • Basic knowledge of Kotlin programming language
  • Understanding of Android fundamentals (Activities, Fragments)

Setting Up the Navigation Component

Step 1: Add Dependencies

First, you need to add the Navigation Component dependencies to your app's build.gradle file:

kotlin
dependencies {
// Navigation Component
implementation("androidx.navigation:navigation-fragment-ktx:2.7.5")
implementation("androidx.navigation:navigation-ui-ktx:2.7.5")

// Feature module support
implementation("androidx.navigation:navigation-dynamic-features-fragment:2.7.5")

// Testing Navigation
androidTestImplementation("androidx.navigation:navigation-testing:2.7.5")
}

Step 2: Create a Navigation Graph

The navigation graph is an XML resource that contains all navigation-related information in one centralized location. It includes destinations (fragments or activities) and actions (connections between destinations).

Create a new resource file: res/navigation/nav_graph.xml

xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/homeFragment">

<fragment
android:id="@+id/homeFragment"
android:name="com.example.myapp.HomeFragment"
android:label="Home"
tools:layout="@layout/fragment_home">

<action
android:id="@+id/action_homeFragment_to_detailFragment"
app:destination="@id/detailFragment" />
</fragment>

<fragment
android:id="@+id/detailFragment"
android:name="com.example.myapp.DetailFragment"
android:label="Details"
tools:layout="@layout/fragment_detail" />

</navigation>

Step 3: Add NavHost to Your Activity Layout

A NavHost is a container for navigation destinations. The default implementation, NavHostFragment, displays fragments.

Add it to your main activity layout (activity_main.xml):

xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />

</androidx.constraintlayout.widget.ConstraintLayout>

Step 4: Set Up Your Activity

In your main activity, set up the Navigation Component:

kotlin
package com.example.myapp

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.NavigationUI

class MainActivity : AppCompatActivity() {

private lateinit var navController: NavController

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController

// Set up ActionBar with NavController
NavigationUI.setupActionBarWithNavController(this, navController)
}

// Handle Up button
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp() || super.onSupportNavigateUp()
}
}

Basic Navigation

To navigate from one fragment to another, use the findNavController() extension function and call navigate() with the appropriate action ID:

kotlin
import androidx.navigation.fragment.findNavController

class HomeFragment : Fragment() {

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

// Assuming you have a button in your layout with ID: btnNavigateToDetail
view.findViewById<Button>(R.id.btnNavigateToDetail).setOnClickListener {
findNavController().navigate(R.id.action_homeFragment_to_detailFragment)
}
}
}

Passing Arguments Between Destinations

You can pass data between destinations using Safe Args, which is a Gradle plugin that generates classes to help you navigate and pass data safely.

Step 1: Add the Safe Args plugin to your project-level build.gradle:

kotlin
buildscript {
dependencies {
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.7.5")
}
}

Step 2: Apply the plugin in your app-level build.gradle:

kotlin
plugins {
id("androidx.navigation.safeargs.kotlin")
}

Step 3: Define arguments in your navigation graph:

xml
<fragment
android:id="@+id/detailFragment"
android:name="com.example.myapp.DetailFragment"
android:label="Details"
tools:layout="@layout/fragment_detail">

<argument
android:name="itemId"
app:argType="integer"
android:defaultValue="-1" />

<argument
android:name="itemName"
app:argType="string"
app:nullable="true" />
</fragment>

Step 4: Navigate with arguments from your source fragment:

kotlin
class HomeFragment : Fragment() {

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

view.findViewById<Button>(R.id.btnNavigateToDetail).setOnClickListener {
val action = HomeFragmentDirections.actionHomeFragmentToDetailFragment(
itemId = 123,
itemName = "Sample Item"
)
findNavController().navigate(action)
}
}
}

Step 5: Retrieve arguments in your destination fragment:

kotlin
class DetailFragment : Fragment() {

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

val args = DetailFragmentArgs.fromBundle(requireArguments())
val itemId = args.itemId
val itemName = args.itemName

// Use the arguments as needed
view.findViewById<TextView>(R.id.tvItemDetails).text =
"Item ID: $itemId, Name: $itemName"
}
}

Advanced Navigation Features

Deep Linking

The Navigation Component supports deep linking, which allows you to navigate directly to a specific fragment with the appropriate back stack.

Add deep links to your navigation graph:

xml
<fragment
android:id="@+id/detailFragment"
android:name="com.example.myapp.DetailFragment"
android:label="Details"
tools:layout="@layout/fragment_detail">

<argument
android:name="itemId"
app:argType="integer" />

<deepLink
app:uri="example://details/{itemId}" />
</fragment>

Then register the intent filter in your AndroidManifest.xml:

xml
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<nav-graph android:value="@navigation/nav_graph" />
</activity>

To integrate the Navigation Component with a Bottom Navigation Bar:

Step 1: Create a menu resource file for your bottom navigation items:

xml
<!-- res/menu/bottom_nav_menu.xml -->
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/homeFragment"
android:icon="@drawable/ic_home"
android:title="Home" />
<item
android:id="@+id/dashboardFragment"
android:icon="@drawable/ic_dashboard"
android:title="Dashboard" />
<item
android:id="@+id/notificationsFragment"
android:icon="@drawable/ic_notifications"
android:title="Notifications" />
</menu>

Step 2: Add BottomNavigationView to your activity layout:

xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@+id/bottom_nav"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />

<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="0dp"
android:layout_marginStart="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/bottom_nav_menu" />

</androidx.constraintlayout.widget.ConstraintLayout>

Step 3: Connect BottomNavigationView with the Navigation Component:

kotlin
class MainActivity : AppCompatActivity() {

private lateinit var navController: NavController

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController

// Connect BottomNavigationView with NavController
val bottomNav = findViewById<BottomNavigationView>(R.id.bottom_nav)
NavigationUI.setupWithNavController(bottomNav, navController)
}
}

Real-World Example: Building a Simple News App

Let's put everything together by building a simple news app with the Navigation Component.

Step 1: Create the Navigation Graph

xml
<!-- res/navigation/nav_graph.xml -->
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/newsListFragment">

<fragment
android:id="@+id/newsListFragment"
android:name="com.example.newsapp.NewsListFragment"
android:label="News"
tools:layout="@layout/fragment_news_list">

<action
android:id="@+id/action_newsList_to_newsDetail"
app:destination="@id/newsDetailFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
</fragment>

<fragment
android:id="@+id/newsDetailFragment"
android:name="com.example.newsapp.NewsDetailFragment"
android:label="News Details"
tools:layout="@layout/fragment_news_detail">

<argument
android:name="newsId"
app:argType="string" />

<argument
android:name="newsTitle"
app:argType="string" />

<deepLink app:uri="example://news/{newsId}" />
</fragment>

<fragment
android:id="@+id/bookmarksFragment"
android:name="com.example.newsapp.BookmarksFragment"
android:label="Bookmarks"
tools:layout="@layout/fragment_bookmarks">

<action
android:id="@+id/action_bookmarks_to_newsDetail"
app:destination="@id/newsDetailFragment" />
</fragment>

<fragment
android:id="@+id/settingsFragment"
android:name="com.example.newsapp.SettingsFragment"
android:label="Settings"
tools:layout="@layout/fragment_settings" />
</navigation>

Step 2: Create the Bottom Navigation Menu

xml
<!-- res/menu/bottom_nav_menu.xml -->
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/newsListFragment"
android:icon="@drawable/ic_news"
android:title="News" />
<item
android:id="@+id/bookmarksFragment"
android:icon="@drawable/ic_bookmark"
android:title="Bookmarks" />
<item
android:id="@+id/settingsFragment"
android:icon="@drawable/ic_settings"
android:title="Settings" />
</menu>

Step 3: Implement the News List Fragment

kotlin
class NewsListFragment : Fragment() {

private val newsList = listOf(
News("1", "Android 12 Released", "Google releases new version of Android"),
News("2", "Kotlin 1.5 Features", "Explore the new features in Kotlin 1.5"),
News("3", "Jetpack Compose 1.0", "Jetpack Compose reaches stable release")
)

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_news_list, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

val recyclerView = view.findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(requireContext())
recyclerView.adapter = NewsAdapter(newsList) { news ->
val action = NewsListFragmentDirections.actionNewsListToNewsDetail(
newsId = news.id,
newsTitle = news.title
)
findNavController().navigate(action)
}
}
}

data class News(val id: String, val title: String, val description: String)

class NewsAdapter(
private val newsList: List<News>,
private val onNewsClick: (News) -> Unit
) : RecyclerView.Adapter<NewsAdapter.NewsViewHolder>() {

// Implementation of adapter methods...

inner class NewsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(news: News) {
itemView.findViewById<TextView>(R.id.tvNewsTitle).text = news.title
itemView.findViewById<TextView>(R.id.tvNewsDescription).text = news.description

itemView.setOnClickListener {
onNewsClick(news)
}
}
}
}

Step 4: Implement the News Detail Fragment

kotlin
class NewsDetailFragment : Fragment() {

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_news_detail, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

val args = NewsDetailFragmentArgs.fromBundle(requireArguments())

view.findViewById<TextView>(R.id.tvNewsTitle).text = args.newsTitle
view.findViewById<TextView>(R.id.tvNewsId).text = "News ID: ${args.newsId}"

// In a real app, you would fetch the full news details using the ID

view.findViewById<Button>(R.id.btnBookmark).setOnClickListener {
// Add to bookmarks logic
Toast.makeText(context, "News saved to bookmarks", Toast.LENGTH_SHORT).show()
}
}
}

Step 5: Set Up the Main Activity

kotlin
class MainActivity : AppCompatActivity() {

private lateinit var navController: NavController
private lateinit var appBarConfiguration: AppBarConfiguration

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController

// Define top-level destinations to avoid showing the Up button
appBarConfiguration = AppBarConfiguration(
setOf(R.id.newsListFragment, R.id.bookmarksFragment, R.id.settingsFragment)
)

// Set up ActionBar with NavController
setupActionBarWithNavController(navController, appBarConfiguration)

// Set up Bottom Navigation with NavController
val bottomNav = findViewById<BottomNavigationView>(R.id.bottom_nav)
bottomNav.setupWithNavController(navController)
}

override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
}

Summary

In this tutorial, we've covered the Navigation Component in Kotlin Android development. We learned:

  1. How to set up the Navigation Component with the required dependencies
  2. Creating a navigation graph to define destinations and actions
  3. Implementing navigation between fragments
  4. Passing arguments safely between destinations using Safe Args
  5. Adding deep links to navigate directly to specific screens
  6. Integrating the Navigation Component with BottomNavigationView
  7. Building a real-world example of a news app with the Navigation Component

The Navigation Component provides a consistent way to handle navigation in Android applications, making it easier to manage complex navigation flows, handle deep links, and build a better user experience.

Additional Resources

Exercises

  1. Extend the news app example to include search functionality with a search fragment.
  2. Implement animations for transitions between different fragments.
  3. Add a settings screen that allows users to change app preferences.
  4. Create a nested navigation graph for a multi-step registration flow.
  5. Implement a drawer navigation alongside the bottom navigation in the news app example.

Good luck with your Android development journey using the Navigation Component!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)