Android JetPack – Navigation Architecture

Filed Under: Android

In this tutorial, we’ll be discussing the Navigation Architecture that is a part of the JetPack.
JetPack is a set of components and libraries introduced at Google I/O 2018 to build better applications.

Android Jetpack consists of the following components:

android jetpack components

Android JetPack Navigation Architecture Component

The Navigation Architecture component is a part of the new AndroidX package that’s introduced since Android SDK 28.

This component consists of new guidelines to structure your application, especially navigation between Fragments.

Google recommends the Single Activity Architecture moving forward when using JetPack.
Navigation Architecture gets ride of the complex FragmentTranscation by providing its own set of classes to navigate between fragments. Let’s first look into the guidelines for Navigation Architecture.

Principles of Navigation

  • The app should have a fixed starting destination.
  • A stack is used to represent the navigation state of an app.
  • The Navigation Up function never exits your app.
  • Up and Back should function identical in all the other cases.
  • Deep linking to a destination or navigating to the same destination should yield the same stack.

Getting Started

Create a new Android Studio Project. Add the following classpath inside the root build.gradle file:


buildscript {
	...
	repositories {
    		google()
	}
	dependencies {
    		...
    		classpath 'android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha04'
	}
}

Inside the app’s build.gradle add the following:


implementation 'android.arch.navigation:navigation-fragment:1.0.0-alpha04'
implementation 'android.arch.navigation:navigation-ui:1.0.0-alpha04'

Add the following plugin inside app’s build.gradle:


apply plugin: 'androidx.navigation.safeargs'

Project Structure

android jetpack navigation project structure

Our project consists of a single activity and two fragments. Let’s see how we arrived at this.

Navigation Graph

A Navigation Graph is the core layer of the Navigation Architecture. It lays out all the fragments/activities and adds all the connections between them.

Within this graph, we address different parts using a few key terms:

Navigation Graph XML – This is an XML defined which is created inside the res folder. Right click on the res directory and choose New -> Android resource file. Set the title for the file and choose Navigation from the Resource type dropdown as shown below:

android navigation graph xml

This creates a navigation folder inside the res with the relevant file name.

Navigation Host Fragment
Inside the Activity layout, we define a Navigation Host Fragment. In our project, we’ve set the navigation host fragment inside the activity_main.xml layout as shown below:


<?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/my_nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/navigation_graph" />


</androidx.constraintlayout.widget.ConstraintLayout>
  • app:navGraph: defines which Navigation Graph will be associated with the Navigation Host
  • app:defaultNavHost="true": ensures that the Navigation Host intercepts the system back button when pressed.

Destination – A Destination is any fragment or activity where the user can go.

We can add destinations inside the Navigation Graph Design file as:

android navigation graph destinations

  • On the left, we have the Destinations
  • In the middle, we have the Navigation Graph
  • On the right, we have the Attributes editor. Inside the attributes editor, we can add actions, pass arguments

Notice the circular icon on the FirstFragment when it is highlighted. These are Actions.

Actions – These are defined to navigate from one fragment/activity to another. We can define them by dragging or in the XML.

android jetpack navigation actions

An action is created with an id specified. You can choose the transition animations as well from a dropdown list of built-in ones or create your own and specify it.
The XML code gets auto-generated:


<?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/navigation_graph"
    app:startDestination="@id/firstFragment">

    <fragment
        android:id="@+id/firstFragment"
        android:name="com.journaldev.androidjetpacknavigation.FirstFragment"
        android:label="navigation_first_fragment"
        tools:layout="@layout/navigation_first_fragment" >
        <action
            android:id="@+id/action_firstFragment_to_secondFragment"
            app:destination="@id/secondFragment"
            app:enterAnim="@anim/nav_default_enter_anim"  />
    </fragment>
    <fragment
        android:id="@+id/secondFragment"
        android:name="com.journaldev.androidjetpacknavigation.SecondFragment"
        android:label="navigation_second_fragment"
        tools:layout="@layout/navigation_second_fragment" />
</navigation>

The above code can be written directly without the need of the Design Editor.
We’ve added an animation on the action.

Inside the Navigation Graph XML tag, you must specify the startDestination. The application gets launched there.

The layouts for the First and Second Fragment are:

navigation_first_fragment.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorPrimary"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".FirstFragment">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:gravity="center"
        android:textSize="22sp"
        android:textColor="@android:color/white"
        android:text="This is First Fragment" />

    <Button
        android:id="@+id/button_frag1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Go to next screen" />
</LinearLayout>

navigation_second_fragment.xml


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorAccent"
    android:gravity="center"
    tools:context=".SecondFragment">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:gravity="center"
        android:text="This is Second Fragment"
        android:textColor="@android:color/white"
        android:textSize="22sp" />

    <Button
        android:id="@+id/button_frag2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="Back" />

</RelativeLayout>

Now that the layouts are ready, let’s see how to navigate from one Fragment to the other via the actions.
NavController manages app navigation within the NavHost.

The code for the FirstFragment.java class is:


package com.journaldev.androidjetpacknavigation;

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.NavDirections;
import androidx.navigation.Navigation;

public class FirstFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.navigation_first_fragment, container, false);
    }


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


        View.OnClickListener s = Navigation.createNavigateOnClickListener(R.id.action_firstFragment_to_secondFragment);
        Button button = view.findViewById(R.id.button_frag1);
        button.setOnClickListener(s);
    }

}

We pass the action id inside the createNavigateOnClickListener method. There are a few alternative ways to navigate as well:

Alternative Way 1
We can navigate using the following code as well:


final NavController navController = Navigation.findNavController(getActivity(), R.id.my_nav_host_fragment);

button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                navController.navigate(R.id.action_firstFragment_to_secondFragment);
            }
        });

Alternative Way 2


final NavDirections navDirections = FirstFragmentDirections.actionFirstFragmentToSecondFragment();
final NavController navController = Navigation.findNavController(getActivity(), R.id.my_nav_host_fragment);

button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                navController.navigate(navDirections);
            }
        });

Passing Arguments

The navigation architecture comes with a built-in way to pass type-safe arguments from one Fragment to the other.

You can define them in the Navigation Graph design editor as:

android navigation graph args

and/or in XML:


<fragment
        android:id="@+id/firstFragment"
        android:name="com.journaldev.androidjetpacknavigation.FirstFragment"
        android:label="navigation_first_fragment"
        tools:layout="@layout/navigation_first_fragment" >

        <argument
            android:name="test_string"
            android:defaultValue="hello world"
            app:argType="string" />
    </fragment>

app:argType should be used in place of app:type. app:type is deprecated.

Now when we navigate these args get automatically passed.
We can also pass more arguments programmatically.

Our onViewCreated method of the FirstFragment now becomes:


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


        final Bundle bundle = new Bundle();
        bundle.putBoolean("test_boolean", true);

        final NavController navController = Navigation.findNavController(getActivity(), R.id.my_nav_host_fragment);

        
        Button button = view.findViewById(R.id.button_frag1);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                navController.navigate(R.id.action_firstFragment_to_secondFragment, bundle);
            }
        });
    }

The code for the SecondFragment.java class is:


package com.journaldev.androidjetpacknavigation;

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

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

public class SecondFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.navigation_second_fragment, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        
        Toast.makeText(getActivity().getApplicationContext(), "Bundle args " + getArguments().getBoolean("test_boolean"), Toast.LENGTH_SHORT).show();
        Toast.makeText(getActivity().getApplicationContext(), "Bundle args " + FirstFragmentArgs.fromBundle(getArguments()).getTestString(), Toast.LENGTH_SHORT).show();

        Button button = view.findViewById(R.id.button_frag2);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                final NavController navController = Navigation.findNavController(getActivity(), R.id.my_nav_host_fragment);
                navController.navigateUp();

                navController.addOnNavigatedListener(new NavController.OnNavigatedListener() {
                    @Override
                    public void onNavigated(@NonNull NavController controller, @NonNull NavDestination destination) {
                        Log.d("TAG", destination.getLabel() + " ");
                    }
                });
            }
        });
    }
    
}

The arguments passed in the Navigation Graph have auto-generated getters when you re-build the project.

FirstFragmentArgs.fromBundle(getArguments()).getTestString() is used to retrieve the argument passed from the FirstFragment.
navigateUp() is used to return to the previous fragment.
addOnNavigatedListener is called when the navigation is completed.

The output of the application in action is:

android jetpack navigation output

This brings an end to this tutorial. You can download the project from the link below:

Comments

  1. Monika shama says:

    what is FirstFragmentArgs? why its showing error FirstFragmentArgs.fromBundle()

    1. rahul gupta says:

      FirstFragmentArgs.fromBundle() put it inside braces like FirstFragmentArgs.fromBundle(getArguments()).getTestSting()

      FirstFragmentArgs it is used to access args of fragment defined directly inside nav graph

  2. Monika shama says:

    what is FirstFragmentArgs?
    I am getting an error when I use below code

    FirstFragmentArgs.fromBundle(getArguments()).getTestString(), Toast.LENGTH_SHORT).show();

  3. Sathish Gadde says:

    How to manage fragment state duting backstack. Like first contains recyclerview and user scroll to end then onitemclick navigate to second screen. now if user back to previous fragment there is not state managed. it is possible or not.if possible how to manage ?

    1. alterego says:

      I have same program.Did you fix it?

  4. Oleksandr says:

    thank you! Clear!

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages