ZOFTINO.COM android and web dev tutorials

Navigation in Android App Using Navigation Component

Android navigation component, which is one of the architecture components, can be used to implement navigation in android applications. Using navigation component, you can separate navigation flow logic from the other parts of task code. This way navigation flow can be easily modified without having to make many changes in the code. It also eliminates the need to write boilerplate code to link fragments or activities in applications.

In this tutorial, you can learn about navigation component and how to implement navigation using navigation component with an example.

Table of Contents

How Navigation Component Works

A task or a feature in an app usually consists of several screens where data is viewed, selected or entered. In android app each screen is implemented as a Fragment or an Activity.

When user starts a task, screens or destination in the flow to complete the task are shown one after another. Usually a button click or an item selection handler starts next fragment or activity (destination) in the flow.

With Navigation component framework, all the destinations of a flow are defined in the xml, which includes the definition for linking of destinations. All that needs to be done in the code to show the next screen in the flow in response to an event on a UI widget of current screen is to use Navigation component class and passing next screen destination ID defined in the xml to it.

The xml which contains navigation info needs to be created in navigation resource folder. To define destinations and links in xml, you can directly add corresponding xml elements to it in text view or it can be created using the navigation editor which can be accessed by opening the navigation xml file and clicking the design tab.

android navigation component navigation graph design

Following navigation graph xml contains two fragment destinations. Starting destination can be defined by assigning a destination id to startDestination attribute of navigation root element. In the below example searchFlights destination is the starting destination. The destination searchFlights is linked to flightsList destination by adding action element to searchFlights destination and assigning flightsList destination id to destination attribute of the action element.

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    app:startDestination="@id/searchFlights">
    <fragment
        android:id="@+id/searchFlight"
        android:name="com.zoftino.flights.SearchFlightsFragment"
        android:label="search_flights"
        tools:layout="@layout/search_flight" >
        <action
            android:id="@+id/action_search_to_list"
            app:destination="@id/flightsList" />
    </fragment>
    <fragment
        android:id="@+id/flightsList"
        android:name="com.zoftino.flights.FlightsListFragment"
        android:label="flights_list_fragment"
        tools:layout="@layout/flights_list_fragment" />
</navigation>

After navigation xml is defined, navigation definition needs to be added to the activity which manages the flow of the task. This is done by defining a fragment in the activity layout using NavHostFragment class provided by navigation framework as fragment and adding the navigation graph to it by setting navGraph attribute to point to resource id of navigation 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"
    tools:context="zoftino.com.firestore.FlightActivity">
    <fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/flight_nav_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:navGraph="@navigation/flight_nav_graph"
        app:defaultNavHost="true"/>
</LinearLayout>

After the navigation xml is tied to an activity, destinations defined in the xml needs to be linked to UI widgets starting from the start destination so that next screens are shown in response to the events of the widgets on previous pages. This can be done using NavController class. You need to call navigate method on NavController by passing the destination id in the widget event handler, for example button click handler.

searchFlightsButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Navigation.findNavController(view).navigate(R.id.action_search_to_list);
    }
});

That is how destinations are defined and connected to enable navigation in android apps using navigation component.

Setup

You need to add following navigation component dependencies to your module’s build.gradle file.

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

To use navigation component editor, which helps you in creating navigation graph, you need to install android studio 3.2 or later versions and enable navigation editor by going to settings, experimental and checking Enable Navigation Editor option.

android

Following example shows how to use navigation component. It uses shopping and checkout tasks and defines two navigation graphs for each task. Navigation graph contains fragment and activity destinations. Shopping flow contains products list fragment destination and product details fragment destination. Checkout flow contains address fragment and payment activity.

Menu options to access these two tasks are provided as options menu on app bar.

android navigation component fragment to fragment and fragment to action navigation example

Navigation graph (shopping_nav_graph)

Here is the navigation graph for shopping flow.

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    app:startDestination="@id/productListDestination">
    <fragment
        android:id="@+id/productListDestination"
        android:name="com.zoftino.navigation.ProductsListFragment"
        android:label="product_list"
        tools:layout="@layout/products_list_layout">
        <action
            android:id="@+id/action_list_details"
            app:destination="@id/productDetailsDestination"/>
    </fragment>
    <fragment
        android:id="@+id/productDetailsDestination"
        android:name="com.zoftino.navigation.ProductDetailsFragment"
        android:label="product_details"
        tools:layout="@layout/product_details" />
</navigation>

ProductsListFragment

public class ProductsListFragment  extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.products_list_layout,
                container, false);

        ((Button)view.findViewById(R.id.product_details_b))
                .setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Navigation.findNavController(view).navigate(R.id.action_list_details);
            }
        });
        return view;
    }
}

public class ProductDetailsFragment  extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.product_details,
                container, false);
        return view;
    }
}

Navigation graph (checkout_nav_graph)

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    app:startDestination="@id/addressDestination">
    <fragment
        android:id="@+id/addressDestination"
        android:name="com.zoftino.navigation.AddressFragment"
        android:label="address"
        tools:layout="@layout/address">
        <action
            android:id="@+id/action_address_payment"
            app:destination="@id/paymentDestination"/>
    </fragment>
    <activity
        android:id="@+id/paymentDestination"
        android:name="com.zoftino.navigation.PaymentActivity"
        android:label="payment"
        tools:layout="@layout/payment_activity" />
</navigation>

AddressFragment

public class AddressFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.address,
                container, false);

        ((Button)view.findViewById(R.id.payment_b)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Navigation.findNavController(view).navigate(R.id.action_address_payment);
            }
        });
        return view;
    }
}

PaymentActivity

public class PaymentActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.payment_activity);
    }
}

Main activity

The main activity inflates options menu which provides options for user to start either shopping flow or checkout flow. Launcher activity layout contains frame layout which works as a place holder for fragment destinations of the selected flow. The fragment (NavHostFragment) defined in the activity layout points to shopping flow navigation graph, so the starting screen will be product list fragment.

public class ShoppingActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.shopping_activity);
    }
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.shop_menu, menu);
        return true;
    }
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.products:
                switchNavGraph(R.navigation.shopping_nav_graph);
                return true;
            case R.id.checkout:
                switchNavGraph(R.navigation.checkout_nav_graph);
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
    private void switchNavGraph(int navResourceId){
        NavHostFragment finalHost = NavHostFragment.create(navResourceId);
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.content_frame, finalHost)
                .setPrimaryNavigationFragment(finalHost)
                .commit();
    }
}

Main activity layout(shopping_activity)

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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">
    <FrameLayout
        android:id="@+id/content_frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/shopping_nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:navGraph="@navigation/shopping_nav_graph"
        app:defaultNavHost="true"/>
    </FrameLayout>
</android.support.constraint.ConstraintLayout>

Menu (shop_menu)

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item android:id="@+id/products"
        android:title="Shop"
        app:showAsAction="never"/>
    <item android:id="@+id/checkout"
        android:title="Checkout"
        app:showAsAction="never"/>
</menu>

Destinations defined in the navigation graph can be tied to menu items of bottom navigation view. To do that, menu item ids should match to the ids of destinations defined in the navigation graph. In the code, you need to use NavigationUI class to integrate menu items and destinations.

NavigationUI.setupWithNavController(bottomNavigationView,
        navHostFragment.getNavController());
Let’s see how to integrate BottomNavigationView and navigation component with an example. In this example, shopping and checkout flow destinations are defined in one navigation graph and two menu items defined for bottom navigation view connect to start destination of each flow. android navigation component bottom navigation view example

Activity

public class ShoppingBottomNavActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.shopping_bottom_nav_activity);

        BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_nav);
        NavHostFragment navHostFragment = (NavHostFragment)getSupportFragmentManager()
                .findFragmentById(R.id.shop_nav_host_fragment);
        NavigationUI.setupWithNavController(bottomNavigationView,
                navHostFragment.getNavController());
    }
}

Activity Layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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=".ShoppingBottomNavActivity">

    <fragment
        android:id="@+id/shop_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/shop_checkout_nav_graph" />

    <android.support.design.widget.BottomNavigationView
        android:id="@+id/bottom_nav"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/shop_bottom_nav_menu" />
</android.support.constraint.ConstraintLayout>

Menu

Ids of the menu items are same as ids of destinations. Menu item shop points to starting destination of shopping flow and checkout menu item points to starting destination of checkout flow.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/productListDestination"
        android:title="Shop"
        android:icon="@drawable/shop"/>
    <item android:id="@+id/addressDestination"
        android:title="Checkout"
        android:icon="@drawable/checkout"/>
</menu>

Like bottom navigation view is used with navigation component, navigation component can be integrated with navigation drawer in the same way.

android navigation component navigation drawer example

Activity

public class ShoppingNavigationDrawerActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.shopping_nav_drawer_activity);

        NavigationView navigationView = findViewById(R.id.navigationView);
        NavHostFragment navHostFragment = (NavHostFragment)getSupportFragmentManager()
                .findFragmentById(R.id.shop_nav_host_fragment);

        NavigationUI.setupWithNavController(navigationView,
                navHostFragment.getNavController());
    }
}

Layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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=".ShoppingNavigationDrawerActivity">
    <android.support.v4.widget.DrawerLayout
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    <fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/shop_nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:navGraph="@navigation/shop_checkout_nav_graph"
        app:defaultNavHost="true"/>
        <android.support.design.widget.NavigationView
            android:id="@+id/navigationView"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_gravity="start"
            app:menu="@menu/shop_bottom_nav_menu"/>
    </android.support.v4.widget.DrawerLayout>
</android.support.constraint.ConstraintLayout>

Passing Data between Destinations

Data can be passed between destinations. For that, in navigation graph, you need to define arguments in the definition of the fragment which receives the data from source destination.

In the following example, MovieDetailsFragment can receive arugment.

<fragment
    android:id="@+id/movieDetailsDestination"
    android:name="com.zoftino.navigation.MovieDetailsFragment"
    android:label="movie details"
    tools:layout="@layout/movie_details">
    <argument
        android:name="movieId"
        android:defaultValue="ab12" />
</fragment>

In the source fragment, where navigation to the next destination happens, you need to create a bundle and add the argument to it and pass it to navigate method of NavController.

Bundle bundle = new Bundle();
bundle.putString("movieId", "DBS183");
Navigation.findNavController(view).navigate(R.id.action_movies_details, bundle);

In the destination fragment, get the data sent from the source fragment.

details_tv.setText("Movie details of "+getArguments().getString("movieId"));

Transitions between Destinations

You can apply animations to the transitions between destinations. You can add animation to the action in the nav graph using enterAnim and exitAnim attributes of action element.

<fragment
    android:id="@+id/moviesDestination"
    android:name="com.zoftino.navigation.MoviesListFragment"
    android:label="movies"
    tools:layout="@layout/movie_list">
    <action
        android:id="@+id/action_movies_details"
        app:destination="@id/movieDetailsDestination"
        app:enterAnim="@anim/action_enter"
        app:exitAnim="@anim/action_exit"/>
</fragment>

You can find complete code here

android component examples