Navigation is part of Android Jetpack which handles everything needed for an app navigation. No need intents to navigate and no need of bundle to pass data.

Destinations are fragments.

1. Navigation graph, will shows all destinations and how they are connected by actions in our app of xml resource type.
2. NavHost, this contains a default NavHostFragment which we will hookup with our activity to display fragments properly.
3. NavController, a controller which is set in NavHost itself to handle all fragment transactions when we navigate to destinations.

What are we building in this app?

  1. Our MainActivity shows default destination fragment with list.
  2. Clicking on item in list opens another fragment with details of that items.
  3. Using NavArgs we will receive the data from list to details fragment.

1. Add navigation dependencies to project

Let’s add dependencies and plugins in project.

implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'

Passing data using SafeArgs between destinations. This requires a additional plugin and one more classpath dependency.

apply plugin: "androidx.navigation.safeargs.kotlin"

Open build.gradle ( Project ) directory and this plugin under existing dependency.

classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5"

Now sync your project to import all required classes.

2. Setup navigation and graph

Let’s create a navigation graph for our app, which has all fragment destinations.

In your project under res folder right click and select New Android Resource File. On dialog box enter
file name : nav_graph. Resource type: Navigation and click ok.
We will get back to graph once we have new destinations(fragments) to add, right now file will be empty.

NavHost has default fragment which is NavHostFragment. We hook this into activity, so let’s add it to activity_main.xml.

activity_main.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">

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph" />

</androidx.constraintlayout.widget.ConstraintLayout>
  • android:id=”@+id/myNavHostFragment”
    We declare attribute id to hook NavigationController with MainActivity.java class
  • android:name=”androidx.navigation.fragment.NavHostFragment”
    Attribute name will change behavior of simple fragment to act as NavHostFragment.
  • app:defaultNavHost=”true”
    Setting value true to this attribute will make this fragment to behave default NavHostFragment.
  • app:navGraph=”@navigation/nav_graph”
    Attach our app navigation graph to this NavHostFragment.

3. Add fragments to graph

If you run app at this point, it will crash because nav_graph has no fragments to show when app opens first time.

Let’s create two empty fragments then we will add to nav_graph as destinations.

  1. ColorListFragment.kt for showing list of items with RecyclerView.
  2. ColorDetailFragment.kt for showing detail for selected item.

Open your nav_graph.xml layout file.
Now click on the icon New Destination, from the list click fragment_color_list to add. That’s it our default host has a fragment to show. Similarly add fragment_color_detail too.

If you run your app, you screen shows ColorListFragment as default destination.

Notice in your nav_graph.xml the attributes will be auto-generated for us.
Each fragment has an id, name and label to show in action bar.
You can change those values for your convenience, take the below reference.

<?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/colorListFragment">

    <fragment
        android:id="@+id/colorListFragment"
        android:name="com.developersbreach.navigationcomponent.ColorListFragment"
        android:label="Color list fragment"
        tools:layout="@layout/fragment_color_list" />

    <fragment
        android:id="@+id/colorDetailFragment"
        android:name="com.developersbreach.navigationcomponent.ColorDetailFragment"
        android:label="Color detail fragment"
        tools:layout="@layout/fragment_color_detail" />

</navigation>

4. Add list and detail data to fragments

To show list with recycler view we need a modal, adapter, item layout. You can copy these classes from below or from my GitHub, link to whole project is below.

Color.kt:

Data type is string because we are showing single textview for each item in list. Also list data is added inside companion object so that we can access it from variable property in adapter.

import android.os.Parcelable
import kotlinx.android.parcel.Parcelize

@Parcelize
data class Color(
    val colorName: String
) : Parcelable {

    companion object {

        val colorList = listOf(
            Color("Red"), Color("Green"), Color("Blue"),
            Color("Yellow"), Color("Black"), Color("White"),
            Color("Brown"), Color("Orange"), Color("Grey"),
            Color("Purple"), Color("Green"), Color("Pink"),
            Color("Amber"), Color("Cyan"), Color("Teal")
        )
    }
}

item_color.xml:

<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/item_color_text_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginHorizontal="12dp"
    android:layout_marginVertical="8dp"
    android:background="#E8E8E8"
    android:paddingHorizontal="16dp"
    android:paddingVertical="12dp"
    android:textAppearance="?attr/textAppearanceHeadline5"
    tools:text="Blue" />

ColorAdapter.kt:

I have also added click listener for adapter items. The list data with variable property name is colorList.

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class ColorAdapter(
    private val onClickListener: OnClickListener
) : RecyclerView.Adapter<ColorAdapter.ColorViewHolder>() {

    private val colorList = Color.colorList

    class ColorViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val colorNameTextView: TextView = itemView.findViewById(R.id.item_color_text_view)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ColorViewHolder {
        return ColorViewHolder(
            LayoutInflater.from(parent.context).inflate(
                R.layout.item_color, parent, false
            )
        )
    }

    override fun onBindViewHolder(holder: ColorViewHolder, position: Int) {
        val color: Color = colorList[position]
        holder.colorNameTextView.text = color.colorName
        holder.itemView.setOnClickListener {
            onClickListener.onClick(color)
        }
    }

    override fun getItemCount() = colorList.size

    class OnClickListener(val clickListener: (color: Color) -> Unit) {
        fun onClick(color: Color) = clickListener(color)
    }
}

Now our data is ready, let’s attach the adapter to recycler view and show data in fragment.

fragment_color_list.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=".ColorListFragment">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/color_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginVertical="8dp"
        android:orientation="vertical"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        tools:listitem="@layout/item_color" />

</androidx.constraintlayout.widget.ConstraintLayout>

ColorListFragment.kt:

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView

class ColorListFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_color_list, container, false)
    }

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

        val recyclerView: RecyclerView = view.findViewById(R.id.color_recycler_view)
        val adapter = ColorAdapter(listener)
        recyclerView.adapter = adapter
    }

    private val listener = ColorAdapter.OnClickListener { color ->
        // Add action to navigate
    }

Now let’s complete adding code to detail fragment.

fragment_color_detail.xml:

Add a simple text view element, when we click item in list that detail will be shown here.

<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/color_name_detail_text_vew"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:textAppearance="?attr/textAppearanceHeadline3"
    android:textColor="#210018"
    tools:context=".ColorDetailFragment"
    tools:text="Blue" />

ColorDetailFragment.kt:

We still need to receive data here from list fragment to show in this fragment.

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView

class ColorDetailFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_color_detail, container, false)
    }

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

        val colorNameTextView: TextView = view.findViewById(R.id.color_name_detail_text_vew)
    }
}

Fragments are ready to show data but it does nothing when we click the items in list, for this we need to add an action in graph let’s do that.

5. Add action and arguments

In navigation graph using actions connect both fragment from list to detail, each action has it’s ID which can be changed from generated attributes.
Once you connect those destinations new tag will be generated, I have changed the action ID.

<action
    android:id="@+id/colorListToDetailFragment"
    app:destination="@id/colorDetailFragment" />

Detail fragment has to receive data at the other end, so let’s add a argument of type Color.kt to it.

In navigation graph select detail fragment and from the right side window click + button in Arguments to add one.
Name of the argument: colorDataArgs
Type: Custom Parcelable(select Color.kt) and then click update.

<argument
    android:name="colorDataArgs"
    app:argType="com.developersbreach.navigationcomponent.Color" />

Below is the reference for completed graph to look like after making those changes.

<?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/colorListFragment">

    <fragment
        android:id="@+id/colorListFragment"
        android:name="com.developersbreach.navigationcomponent.ColorListFragment"
        android:label="Color list fragment"
        tools:layout="@layout/fragment_color_list">
        <action
            android:id="@+id/colorListToDetailFragment"
            app:destination="@id/colorDetailFragment" />
    </fragment>

    <fragment
        android:id="@+id/colorDetailFragment"
        android:name="com.developersbreach.navigationcomponent.ColorDetailFragment"
        android:label="Color detail fragment"
        tools:layout="@layout/fragment_color_detail">
        <argument
            android:name="colorDataArgs"
            app:argType="com.developersbreach.navigationcomponent.Color" />
    </fragment>

</navigation>

Update click listener :

Now finally we have to add the pass the information to click listener to pass the data with action in list fragment. With this you will be able to navigate to detail fragment.

// Add action to navigate
findNavController().navigate(
    ColorListFragmentDirections.colorListToDetailFragment(color)
)

6. Receive data with NavArgs

Earlier we have added argument to detail fragment from navigation graph with arg name colorDataArgs. Now we will make use of this to receive data as gradle will auto-generate the object ColorDetailFragmentArgs for us.

ColorDetailFragment.kt:

Make use of navArgs to receive data fromargument generated object in fragment class.

val color: ColorDetailFragmentArgs by navArgs()

Set our textview to receive data from color object. That’s it.

val color: Color = color.colorDataArgs
colorNameTextView.text = color.colorName

Below is the reference for completed detail fragment code.

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.navigation.fragment.navArgs

class ColorDetailFragment : Fragment() {

    private val color: ColorDetailFragmentArgs by navArgs()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_color_detail, container, false)
    }

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

        val colorNameTextView: TextView = view.findViewById(R.id.color_name_detail_text_vew)
        val color: Color = color.colorDataArgs
        colorNameTextView.text = color.colorName
    }
}

Run the app and you should be able to navigate and see data is being passed between these destinations.
If you are not able to navigate make sure you added destinations and actions correctly

7. Add up button to navigate back

Since our MainActivity class is hosting all the fragments we have to give information to handle the UI changes.

7.1. Show Up button in fragment

val navController = findNavController(R.id.nav_host_fragment)
NavigationUI.setupActionBarWithNavController(this, navController)

7.2. Set navigate Up listener

override fun onSupportNavigateUp(): Boolean {
    val navController = findNavController(R.id.nav_host_fragment)
    return navController.navigateUp() || super.onSupportNavigateUp()
}

This is how our MainActivity.kt finally looks like.

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.navigation.findNavController
import androidx.navigation.ui.NavigationUI

class MainActivity : AppCompatActivity() {

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

        val navController = findNavController(R.id.nav_host_fragment)
        NavigationUI.setupActionBarWithNavController(this, navController)
    }

    override fun onSupportNavigateUp(): Boolean {
        val navController = findNavController(R.id.nav_host_fragment)
        return navController.navigateUp() || super.onSupportNavigateUp()
    }
}

Run your app and check if up-button is being shown and working on click.

8. Links for project code and GitHub

Ufff. It was long post with clear information, I put lot of information even for small tasks in this post. If you found difficulty in following me you can get this working sample from GitHub above.


Here We Go Again : (

if (article == helpful) {
    println("Like and subscribe to blog newsletter.")
} else {
    println("Let me know what i should blog on.")
}

This site uses Akismet to reduce spam. Learn how your comment data is processed.