At Google I/O 2018, Android introduced a bunch of new components to the Android Jetpack libraries. One such library is Navigation component, designed to reduce the effort in building apps with consistent navigation flow and to reduce errors and making it easier to work fragments. Recently the Navigation Component moved from beta to stable.

With the navigation component, it’s very easy to build consistent navigation in your apps. Not only this with the navigation component you can do a lot more:

  • Fragment transaction can be done with just a single method call.
  • Handling Up and Back actions correctly by default.
  • Safely pass data between destination with SafeArgs.
  • Native support for deep-linking.
  • Building navigation drawers & bottom navigation bars with minimal code.

So now let’s learn how we can use this component in our apps.

1. Add the dependencies

In your app-level build.gradle file include the following dependencies

implementation "androidx.navigation:navigation-fragment:2.1.0"
implementation "androidx.navigation:navigation-ui:2.1.0"

If you are using kotlin append -ktx to the above libraries

implementation "androidx.navigation:navigation-fragment-ktx:2.1.0"
implementation "androidx.navigation:navigation-ui-ktx:2.1.0"

To add support for SafeArgs, add a classpath in your project-level build.gradle file.

buildscript {
    repositories {
        google()
    }
    dependencies {
        //.....
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.1.0"
    }
}

Now add the following line to your app-level build.gradle file.

apply plugin: "androidx.navigation.safeargs"

2. Add Activity

Create a BaseActivity to contain the fragments. This activity will contain the Host fragment.

Java:

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class BaseActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_base);
    }
}

Kotlin:

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class BaseActivity : AppCompatActivity() {

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

Now add a layout file activity_base.xml that contains a fragment. This fragment will be our NavHost fragment i.e, An empty container to display our destination fragments.

<?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=".ui.BaseActivity">

    <fragment
        android:id="@+id/base_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:defaultNavHost="true" />

</androidx.constraintlayout.widget.ConstraintLayout>

android:name signifies that this fragment is a host for our destination and app:defaultNavHost when set to true tells that this is the default host for all the destination in our app.

3. Add the Navigation Graph

Navigation graph is an XML resource file that contains all the navigation-related information in a single file. It contains all the information about destinations and paths. It can be visually edited from Android Studio 3.3 onwards.

To add the navigation graph: Right-click on the resource(res) folder in the Project menu> select New > select Android Resource File.

Select navigation from the Resource type dropdown and add the filename nav_graph.xml
A visual editor opens up to edit the navigation graph file.

Now add this graph file to the NavHost fragment we just created above as app:navGraph="@navigation/nav_graph"

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

4. Create Destinations

On the navigation graph editor find the add destination button and then click on ‘Create new Destination’.

The first icon with a plus sign is the add destination.

Now a fragment creation window opens up. Let’s name our fragment HomeFragment.

Set the HomeFragment as our start destination by using the home icon in the toolbar.

Now let’s add another destination and name it DestFragment. Now our navigation graph looks like below.

After creating two destinations.

Click on the homeFragment destination and drag a path from homeFragment to destFragment destination.

Drag a path from home to dest.

Let’s add a button to our HomeFragment. When clicking the button the DestFragment should open.

Go to fragment_home.xml & Add a button

<?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=".HomeFragment">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Open DEST"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    
</androidx.constraintlayout.widget.ConstraintLayout>

Now in HomeFragment.kt or HomeFragment.java file override the onViewCreated method and set a listener for the button we just added. Then use the navigate method of NavController object to switch to destFragment destination.

Java

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;

public class HomeFragment extends Fragment {


    public HomeFragment() {
        // Required empty public constructor
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_home, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        final NavController navController =
                Navigation.findNavController(requireActivity(), R.id.base_fragment);

        Button button = getActivity().findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                navController.navigate(R.id.destFragment);
            }
        });
    }
}

Kotlin

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import kotlinx.android.synthetic.main.fragment_home.*

class HomeFragment : Fragment() {

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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        button.setOnClickListener { 
            findNavController().navigate(R.id.destFragment)
        }
    }
}

This is it. See how easy it is to switch fragments. I’ll write about using navigation graph with BottomNavigationBar in my next post in the series.

You can find the sample code used in this article here.

49