Navigation is part of Android Architecture Components which helps to visualize and build an app which navigates properly between different destinations. Navigation supports common patterns like NavigationDrawer, Menu’s and handling and back buttons.

From the above image, We have one activity and two fragments. Our MainActivity class acts as a NavHost or simply a container to host other fragments, since we call it NavHostFragment.

Now our host has a first destination named VehiclesFragment class showing list of vehicles and clicking on each item launches second destination VehicleDetailFragment class, now this navigation between destination happened because of NavController which manages navigation in our app by swapping fragments in the container. We also pass data between these destinations using SafeArgs which we will discuss below. Later user can navigate back to first destination by pressing back or up button.

This video shows how our app looks after all this implementation we do below. So definitely stick with me till the end

Follow this content to organize your project properly and stick with me till the end.

This post will be dealing with Navigation using ViewModel and LiveData if you are looking for much simple example, i wrote another post only using Navigation component you can check my other articles in this site and also one with kotlin.

1. Create project in android studio

Choose you project with Empty Activity and click Next
Project name – Architecture Components Java
Language – Java (I’ll create another post in kotlin)
Choose you Minimum API level (I’ll choose API 21: Lollipop)
Use androidx artifacts, tick the checkbox and Finish

2. Add dependencies for navigation components

We need to add dependencies for navigation in our project along with ViewModel and LiveData.
Also add RecyclerView dependency to display the list.
Make sure your dependencies already includes ConstraintLayout. If not add from below.
Under Gradle Scripts, open build.gradle (Module: app) directory and add below dependencies to it.

// Navigation Component
implementation "androidx.navigation:navigation-fragment:2.3.0"
implementation "androidx.navigation:navigation-ui:2.3.0"
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
// RecyclerView
implementation 'androidx.recyclerview:recyclerview:1.1.0'
// ConstraintLayout
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

Now sync your project to import all required classes.

Since we are building detail screen for every item in list we pass data between fragment destinations. We do this using safe arguments, for this we have to add one more dependency and also apply a gradle plugin. So let’s do it.
In build.gradle ( Module: app ) file navigate to top and add this plugin below existing application plugin:

apply plugin: "androidx.navigation.safeargs"

Do not sync your project at this point since we have to add last one more plugin in project directory.
In build.gradle ( Project: Architecture Components Java ) project directory file under dependencies section add this below plugin:

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

Now sync your project to import all required classes

You will not find any errors in gradle files if you followed properly. If something is broken just clean your project repeat these steps again it must work. Run your app in device or emulator to test, now all it shows is just a Hello World! message.
Take a look at this below code to compare these changes you have made.

build.gradle (:app)

apply plugin: 'com.android.application'
apply plugin: "androidx.navigation.safeargs"

android {
    compileSdkVersion 30
    buildToolsVersion "29.0.3"
    defaultConfig {
        applicationId "com.developersbreach.architecturecomponentsjava"
        minSdkVersion 21
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    // Navigation Component
    implementation "androidx.navigation:navigation-fragment:2.3.0"
    implementation "androidx.navigation:navigation-ui:2.3.0"
    // ViewModel and LiveData
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
    // RecyclerView
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    // ConstraintLayout
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
}

build.gradle project

buildscript {

    repositories {
        google()
        jcenter()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:4.0.1'
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.0"
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

3. Create Navigation graph:

In navigation component we treat each screen as a destination and we connect those destinations using actions in navigation graph.
Navigation Graph has a visualize structure of our whole app which shows how it builds connections to different destinations.

So let’s create a graph for our app which is basically a xml file under resources folder.
In your project under res folder right click and select New Android Resource File. On dialog box enter
file name –> navigation
resource type –> Navigation then click ok.

4. Add NavHostFragment to activity

Navigation component has default fragment which is NavHostFrgament which hosts all fragments in navigation graph. Let’s add NavHostFrgament to our MainActivity class which adds or removes the fragments automatically using NavController, please read comments provided inside the xml file itself for every attributes.

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/navigation"
        tools:ignore="FragmentTagUsage" />

</androidx.constraintlayout.widget.ConstraintLayout>

Now we have our navigation graph and host let’s create fragments and connect them. But before that, let’s see how architecture component help us build this project.

5. Add resources into project

colors.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#607d8b</color>
    <color name="colorPrimaryDark">#455a64</color>
    <color name="colorVehicle">#90a4ae</color>
</resources>

strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Architecture Components Java</string>
    <string name="motor_cycle">Motorcycle</string>
</resources>

We need sample images to show in our list, i got some for you. Download those images from this link and add them to your drawable folder.

6. Organize classes with packages

Since we are making app which follows architecture components we have to organize project files and classes meaningfully so that other developers can understand your code. So let’s separate them into packages.

I will be creating three packages in project to organize classes separately based on their type. You can do this by right clicking on your project folder in new click onto package then mention package name, you can check this image below to compare how it should look like.

Packages project name folder.

Note: If you run your app at this point it will crash because we still didn’t add any starting destination in graph.

7. Create destination fragments and layouts

In this app we create two fragments, in first(VehiclesFragment.java) one we will show a list of vehicles using RecyclerView and wherever user clicks the item it will navigate to second(VehicleDetailFragment.java) fragment of that item.

So let’s create a fragment with name VehiclesFragment.java with matching layout fragment_vehicles.xml under package main folder.

VehiclesFragment.java:

public class VehiclesFragment extends Fragment {

    public  VehiclesFragment() {
        // 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_vehicles, container, false);
    }
}

fragment_vehicles.xml:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 
    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=".main.VehiclesFragment">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

Similarly create another fragment with name VehicleDetailFragment.java and layout fragment_vehicle_detail.xml under package detail folder like above.

8. Create model , adapter and ViewModel class

Since we are showing a list of items we need a java model class.
In this class we will add private string variable to show vehicle name and private integer variable for vehicle image.
Also add a public constructor with parameters and add getters.
Since we are extending app to pass data between destinations it is good idea to make this class parcelable, we simple do this by implementing Parcelable and importing required methods. You can see how this changes appear below.

Under package Model create class with name Vehicle.java

Vehicle.java:

import android.os.Parcel;
import android.os.Parcelable;

public class Vehicle implements Parcelable {

    private int mVehicleImage;
    private String mVehicleName;

    public Vehicle(int vehicleImage, String vehicleName) {
        this.mVehicleImage = vehicleImage;
        this.mVehicleName = vehicleName;
    }

    public int getVehicleImage() {
        return mVehicleImage;
    }

    public String getVehicleName() {
        return mVehicleName;
    }

    public static final Creator<Vehicle> CREATOR = new Creator<Vehicle>() {
        @Override
        public Vehicle createFromParcel(Parcel in) {
            return new Vehicle(in);
        }

        @Override
        public Vehicle[] newArray(int size) {
            return new Vehicle[size];
        }
    };

    private Vehicle(Parcel in) {
        mVehicleImage = in.readInt();
        mVehicleName = in.readString();
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeInt(mVehicleImage);
        parcel.writeString(mVehicleName);
    }
}

Layout we use inside adapter’s ViewHolder to show each item in our list. Right now we have two child views inside ConstraintLayout a ImageView with id vehicle_image_item_view to show image of vehicle and TextView for it’s name and layout is wrapped with CardView to make it view look better, if android studio shows error for using CardView make sure you have suitable dependencies.

Create a layout under res –> layout folder and name it item_vehicle.
xml code available below.

item_vehicle.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
    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="wrap_content"
    app:cardCornerRadius="4dp"
    app:cardElevation="4dp"
    app:cardUseCompatPadding="true">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="8dp">

        <ImageView
            android:id="@+id/vehicle_image_item_view"
            android:layout_width="120dp"
            android:layout_height="120dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:srcCompat="@drawable/ic_motorcycle"
            tools:ignore="ContentDescription" />

        <TextView
            android:id="@+id/vehicle_name_text_item_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:textColor="@color/colorPrimary"
            android:textSize="20sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="@id/vehicle_image_item_view"
            app:layout_constraintStart_toStartOf="@id/vehicle_image_item_view"
            app:layout_constraintTop_toBottomOf="@id/vehicle_image_item_view"
            tools:text="Motorcycle" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.cardview.widget.CardView>

Create class with name VehicleAdapter in package main which extends into RecyclerView. This class has 3 constructors to show a list, passing context and click listener interface. Copy this java code below into your class.

VehicleAdapter.java:

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.developersbreach.architecturecomponentsjava.R;
import com.developersbreach.architecturecomponentsjava.model.Vehicle;

import java.util.List;

public class VehicleAdapter extends RecyclerView.Adapter<VehicleAdapter.VehicleViewHolder> {

    private List<Vehicle> mVehicleList;
    private VehicleAdapterListener mListener;

    VehicleAdapter(List<Vehicle> vehicleList, VehicleAdapterListener listener) {
        this.mVehicleList = vehicleList;
        this.mListener = listener;
    }

    public interface VehicleAdapterListener {
        void onVehicleSelected(Vehicle vehicle, View view);
    }

    class VehicleViewHolder extends RecyclerView.ViewHolder {

        private ImageView mVehicleImageView;
        private TextView mVehicleNameTextView;

        VehicleViewHolder(@NonNull final View itemView) {
            super(itemView);
            mVehicleImageView = itemView.findViewById(R.id.vehicle_image_item_view);
            mVehicleNameTextView = itemView.findViewById(R.id.vehicle_name_text_item_view);

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    mListener.onVehicleSelected(mVehicleList.get(getAdapterPosition()), itemView);
                }
            });
        }
    }

    @NonNull
    @Override
    public VehicleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.item_vehicle, parent, false);
        return new VehicleViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull VehicleViewHolder holder, int position) {
        Vehicle vehicle = mVehicleList.get(position);
        holder.mVehicleImageView.setImageResource(vehicle.getVehicleImage());
        holder.mVehicleNameTextView.setText(vehicle.getVehicleName());
    }

    @Override
    public int getItemCount() {
        return mVehicleList.size();
    }
}

ViewModel help us save the fragment state. In this class we add data to our list and load into VehiclesFragment.java by hooking up together.

Note: Make sure this class is always public.

VehicleFragmentViewModel.java:

import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.developersbreach.architecturecomponentsjava.R;
import com.developersbreach.architecturecomponentsjava.model.Vehicle;
import java.util.ArrayList;
import java.util.List;

public class VehicleFragmentViewModel extends ViewModel {

    private MutableLiveData<List<Vehicle>> mVehicleData;

    // Get vehicle list of data return type MutableLiveData
    MutableLiveData<List<Vehicle>> getVehicleData() {
        mVehicleData = new MutableLiveData<>();
        loadAllVehicles();
        return mVehicleData;
    }
    
    private void loadAllVehicles() {
        List<Vehicle> vehicleList = new ArrayList<>();
        vehicleList.add(new Vehicle(R.drawable.ic_airport_shuttle, "Airport Shuttle"));
        vehicleList.add(new Vehicle(R.drawable.ic_bike, "Bicycle"));
        vehicleList.add(new Vehicle(R.drawable.ic_boat, "Boat"));
        vehicleList.add(new Vehicle(R.drawable.ic_bus, "Bus"));
        vehicleList.add(new Vehicle(R.drawable.ic_car, "Car"));
        vehicleList.add(new Vehicle(R.drawable.ic_child_friendly, "Child Friendly"));
        vehicleList.add(new Vehicle(R.drawable.ic_flight, "Aeroplane"));
        vehicleList.add(new Vehicle(R.drawable.ic_local_shipping, "Local Shipping"));
        vehicleList.add(new Vehicle(R.drawable.ic_local_taxi, "Local Taxi"));
        vehicleList.add(new Vehicle(R.drawable.ic_motorcycle, "Motorcycle"));
        vehicleList.add(new Vehicle(R.drawable.ic_railway, "Railway"));
        vehicleList.add(new Vehicle(R.drawable.ic_subway, "Subway"));
        mVehicleData.postValue(vehicleList);
    }
}

9. Add list data to first fragment

Now we have our adapter and viewmodel classes let’s attach this to our VehicleFragment.java to show our list.

Open your existing fragment_vehicles.xml file and add RecyclerView tag as child showing grid of images.
We set layoutManager to GridLayoutManager and id to hook with fragment class we will doing this next.
Copy this code in existing layout in res –> layout –> fragment_vehicles.

fragment_vehicles.xml:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 
    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=".main.VehiclesFragment">
    
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/vehicle_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
        app:spanCount="2"
        tools:listitem="@layout/item_vehicle" />

</FrameLayout>

Attach VehicleAdapter and VehicleFragmentViewModel to VehicleFragment.class. For this we need to add few lines of code to existing fragment class.

VehiclesFragment.java:

// Add RecyclerView and find it's id.
private RecyclerView mRecyclerView;
mRecyclerView = view.findViewById(R.id.vehicle_recycler_view);

// Get reference to our ViewModel class using ViewModelProviders and observe vehicleData.
VehicleFragmentViewModel viewModel = new ViewModelProvider(this).get(VehicleFragmentViewModel.class);
        viewModel.getVehicleData().observe(getViewLifecycleOwner(), new Observer<List<Vehicle>>() {
            @Override
            public void onChanged(List<Vehicle> vehicleList) {
                mVehicleAdapter = new VehicleAdapter(vehicleList, new VehicleListener());
                mRecyclerView.setAdapter(mVehicleAdapter);
            }
        });

Implement VehicleAdapterListener which overrides onVehicleSelected.

private class VehicleListener implements VehicleAdapter.VehicleAdapterListener {
    @Override
    public void onVehicleSelected(Vehicle vehicle, View view) {
        // pending implementation
    }
}

Inside the listener we have to pass data and navigate to VehiclesDetailsFragment.
Using Navigation object we find navigation controller with view then we will
call navigate with it’s action name and pass argument to open correct item.
You can change this action name “actionVehiclesFragmentToVehicleDetailFragment”
of your choice in navigation.xml file and make both match properly.

Navigation.findNavController(view).navigate(VehiclesFragmentDirections
.actionVehiclesFragmentToVehicleDetailFragment(vehicle));

Here’s the whole class after making changes:

VehiclesFragment.java:

import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.developersbreach.architecturecomponentsjava.R;
import com.developersbreach.architecturecomponentsjava.model.Vehicle;
import java.util.List;

public class VehiclesFragment extends Fragment {

    private RecyclerView mRecyclerView;
    private VehicleAdapter mVehicleAdapter;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_vehicles, container, false);
        mRecyclerView = view.findViewById(R.id.vehicle_recycler_view);
        return view;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        VehicleFragmentViewModel viewModel = new ViewModelProvider(this).get(VehicleFragmentViewModel.class);
        viewModel.getVehicleData().observe(getViewLifecycleOwner(), new Observer<List<Vehicle>>() {
            @Override
            public void onChanged(List<Vehicle> vehicleList) {
                mVehicleAdapter = new VehicleAdapter(vehicleList, new VehicleListener());
                mRecyclerView.setAdapter(mVehicleAdapter);
            }
        });
    }

    private static class VehicleListener implements VehicleAdapter.VehicleAdapterListener {

        @Override
        public void onVehicleSelected(Vehicle vehicle, View view) {
  
        }
    }
}

10. Add first destinations in graph

Okay now we have our list data ready, let’s add and show the first destination of our app.

Open navigation.xml in navigation package in res folder which you created earlier for this project Below the file navigate to design tab.

Below the file navigate to design tab
You will notice activity_main acts as our nav host fragment. Now click on the icon new destination to add first layout to our graph and choose fragment_vehicles from the list now it will be our first destination.

Now run your app it will be showing the list of items from the layout. Clicking on items will do nothing cause we didn’t add any functionality yet.
You can rotate or navigate away from app to check ViewModel preserves our fragment state correctly.

11. Complete second destination

Let’s add second destination to open VehicleDetail screen on clicking vehicle item list in first destination.
We need ViewModel, ViewModelFactory, fragment_detail.xml and lastly DetailFragment.class in package detail. So let’s add them

Create this class inside package detail and add this code below

DetailFragmentViewModel.java:

import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.MutableLiveData;
import com.developersbreach.architecturecomponentsjava.model.Vehicle;

class DetailFragmentViewModel extends AndroidViewModel {

    private MutableLiveData<Vehicle> selectedVehicle = new MutableLiveData<>();

    DetailFragmentViewModel(@NonNull Application application, Vehicle vehicle) {
        super(application);
        selectedVehicle.postValue(vehicle);
    }

    MutableLiveData<Vehicle> getSelectedVehicle() {
        return selectedVehicle;
    }
}

Create this class inside package detail and this code below, this factory will provide us instance for the ViewModel.

DetailFragmentViewModelFactory.java:

import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import com.developersbreach.architecturecomponentsjava.model.Vehicle;

public class DetailFragmentViewModelFactory implements ViewModelProvider.Factory {

    private Application mApplication;
    private Vehicle mVehicle;

    DetailFragmentViewModelFactory(Application application, Vehicle vehicle) {
        this.mApplication = application;
        this.mVehicle = vehicle;
    }

    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        if (modelClass.isAssignableFrom(DetailFragmentViewModel.class)) {
            return (T) new DetailFragmentViewModel(mApplication, mVehicle);
        }
        throw new IllegalArgumentException("Cannot create Instance for this class");
    }
}

This layout opens showing details of item selected by user. We add a ImageView and TextView for vehicle. Add this code from below.

fragment_vehicle_details.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=".detail.VehicleDetailFragment">

    <ImageView
        android:id="@+id/detail_vehicle_image_view"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_marginTop="80dp"
        tools:src="@drawable/ic_motorcycle"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:ignore="ContentDescription" />

    <TextView
        android:id="@+id/detail_vehicle_name_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:gravity="center"
        tools:text="Motorcycle"
        android:textAppearance="@style/TextAppearance.MaterialComponents.Headline4"
        android:textColor="@color/colorPrimary"
        app:layout_constraintEnd_toEndOf="@+id/detail_vehicle_image_view"
        app:layout_constraintStart_toStartOf="@+id/detail_vehicle_image_view"
        app:layout_constraintTop_toBottomOf="@+id/detail_vehicle_image_view" />

</androidx.constraintlayout.widget.ConstraintLayout>

12. Add second destination in graph

Now our detail layout is ready let’s add this to our navigation graph. Navigate to navigation.xml file and go to design tab from below, add new destination by clicking the icon like you did before and choose fragment_vehicle_detail from the list. Arrange the layouts in your style or just click Auto-Arrange button from top menu bar.
Now select the vehiclesFragment and drag the pointer towards fragment_vehicle_detail layout to connect with vehicleDetailFragment.

13. Connect first and second destination

At this stage we just added destination but our app still doesn’t navigate since we have to pass the arguments and values to detail screen from list. We do this by adding a type argument and name and then later tell our NavController to navigate on clicking the item.
In navigation graph select vehicleDetailFragment and now on to the right side menu find a section for adding arguments.
Clicking plus icon, on the dialog info give Name –> detailFragmentArgs and Type –> Vehicle which is our model class with Parcelable implemented.

You can also do this manually in xml Text file, this is how your file should look like after adding arguments.

navigation.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/navigation"
    app:startDestination="@id/vehiclesFragment">

    <fragment
        android:id="@+id/vehiclesFragment"
        android:name="com.developersbreach.architecturecomponentsjava.main.VehiclesFragment"
        android:label="Vehicles"
        tools:layout="@layout/fragment_vehicles">
        <action
            android:id="@+id/action_vehiclesFragment_to_vehicleDetailFragment"
            app:destination="@id/vehicleDetailFragment" />
    </fragment>

    <fragment
        android:id="@+id/vehicleDetailFragment"
        android:name="com.developersbreach.architecturecomponentsjava.detail.VehicleDetailFragment"
        android:label="Vehicle detail"
        tools:layout="@layout/fragment_vehicle_detail">
        <argument
            android:name="detailFragmentArgs"
            app:argType="com.developersbreach.architecturecomponentsjava.model.Vehicle" />
    </fragment>

</navigation>

After this rebuild the project to load resources for navigating with NavDirections which will be auto-generated by android studio with arguments properly.

14. Pass data using SafeArgs

Attach ViewModel, ViewModelFactory with arguments in VehicleDetailFragment.java class.
We construct our ViewModel class from factory with arguments then we will get data from our ViewModel class and set views in fragment class.
This is how it should look:

We get arguments through bundle, this objects are generated after successful build only.

Application application = requireActivity().getApplication();
Vehicle vehicle = VehicleDetailFragmentArgs.fromBundle(requireArguments()).getDetailFragmentArgs();

Passing arguments into factory constructor which then builds and returns our ViewModel for VehicleDetailFragment.java class.

DetailFragmentViewModelFactory factory = new DetailFragmentViewModelFactory(application, vehicle);
DetailFragmentViewModel viewModel = new ViewModelProvider(this, factory).get(DetailFragmentViewModel.class);

viewModel.getSelectedVehicle().observe(this, new Observer<Vehicle>() {
    @Override
    public void onChanged(Vehicle vehicle) {
        // get results from viewModel and set results to views.
    }
});

This is how our whole VehicleDetailFragment.java class should look like.

VehicleDetailFragment.java:

public class VehicleDetailFragment extends Fragment {

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

        final ImageView vehicleImageView = view.findViewById(R.id.detail_vehicle_image_view);
        final TextView vehicleNameTextView = view.findViewById(R.id.detail_vehicle_name_text_view);

        Application application = requireActivity().getApplication();
        Vehicle vehicle = VehicleDetailFragmentArgs.fromBundle(requireArguments()).getDetailFragmentArgs();
        DetailFragmentViewModelFactory factory = new DetailFragmentViewModelFactory(application, vehicle);
        DetailFragmentViewModel viewModel = new ViewModelProvider(this, factory).get(DetailFragmentViewModel.class);

        viewModel.getSelectedVehicle().observe(getViewLifecycleOwner(), new Observer<Vehicle>() {
            @Override
            public void onChanged(Vehicle vehicle) {
                vehicleImageView.setImageResource(vehicle.getVehicleImage());
                vehicleNameTextView.setText(vehicle.getVehicleName());
            }
        });

        return view;
    }
}

Now our second destination is ready to receive the data and all we need to do is set listener for selected vehicle by calling VehiclesFragmentDirections with action name.
Open VehiclesFragment.java class, inside onVehicleSelected method we do this action using navController, add this line of code:

onVehicleSelected

/*
 * Using Navigation object we find navigation controller with view then we will call
 * navigate with it's action name and pass argument to open correct item. You can change
 * this action name "actionVehiclesFragmentToVehicleDetailFragment" of your choice in 
 * navigation.xml file and make both match properly.
 */
Navigation.findNavController(view).navigate(VehiclesFragmentDirections.actionVehiclesFragmentToVehicleDetailFragment(vehicle));

Now run the app. You can now navigate between those destinations properly.

15. Add up button to navigate back

When you navigate to detail fragment you will notice there is missing up button to navigate back to previous destination, so we will this functionality.

We do this from our MainActivity.class since it hosts NavHostFragment where all the swapping of fragments take place.

Inside onCreate method of MainActivity.java add this code.
First we will find the NavController by setting this as activity first parameter and pass id for host to set this view. We previously defined attribute with id in our activity_main.xml layout in point 5 above.

NavController navigationController = Navigation.findNavController(this, R.id.nav_host_fragment);

We declare this object NavigationUI to call NavController which supports navigation with ActionBar and pass two parameters this as activity and controller which we we defined.

NavigationUI.setupActionBarWithNavController(this, navigationController);

One last step, since our NavController is ready and we override it to return user navigation like up button.

@Override
public boolean onSupportNavigateUp() {
    NavController navigationController = Navigation.findNavController(this, R.id.nav_host_fragment);
    return navigationController.navigateUp();
}

This is how your MainActivity.java class look like after these changes:

MainActivity.java:

import androidx.appcompat.app.AppCompatActivity;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.NavigationUI;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    private NavController mNavController;

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

        mNavController = Navigation.findNavController(this, R.id.nav_host_fragment);
        NavigationUI.setupActionBarWithNavController(this, mNavController);
    }

    @Override
    public boolean onSupportNavigateUp() {
        return mNavController.navigateUp();
    }
}

Now run your app and check if up button is showing and try to navigate between destinations.

That’s it, we have reached the end. For people who thinks this project became complex it’s all beacuse we have added ViewModels, LiveData and ViewModelFactory. If you are need a simple version of this i have wrote another post for this with much simpler view, simple version navigation component.

Here We Go Again :]

if (article was helful) {
    println("Like and subscribe to blog below.")
    println("You will receive email for new articles.")
} else {
    println("Let me know what i should blog on.")
}

16. Project code and documentation

Get started with the Navigation component
Migrate project to navigation
Design Navigation Graphs
ViewModel overview
LiveData overview
Codelabs – Navigation Component
Navigation with simple example

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.