Affiliate links on Android Authority may earn us a commission. Learn more.
Hassle-free fragments: Using Android’s Navigation Architecture Component
During 2018’s I/O conference, Google announced a new approach for developing Android apps.
Google’s official recommendation is to create a single Activity that serves as your app’s main entry point, then deliver the rest of your application’s content as fragments.
While the thought of juggling all those different fragment transactions and life cycles may sound like a nightmare, at I/O 2018 Google also launched the Navigation Architecture Component which is designed to help you adopt this kind of single Activity structure.
In this article, we’ll show you how to add the Navigation component to your project and how you can use it to quickly and easily create a single-Activity, multiple-fragment application, with a little help from Android Studio’s new Navigation Editor. Once you’ve created and connected your fragments, we’ll improve on Android’s standard fragment transitions by using the Navigation component and Editor to create a range of fully-customizable transition animations.
What is the Navigation Architecture Component?
Part of Android JetPack, the Navigation Architecture Component helps you visualize the different routes through your application and simplifies the process of implementing these routes, particularly when it comes to managing fragment transactions.
To use the Navigation component, you’ll need to create a Navigation Graph, which is an XML file describing how your app’s Activities and fragments relate to each other.
A Navigation Graph consists of:
- Destinations: The individual screens the user can navigate to
- Actions: The routes the user can take between your app’s destinations
You can view a visual representation of your project’s Navigation Graph in Android Studio’s Navigation Editor. Below, you’ll find a Navigation Graph consisting of three destinations and three actions as it appears in the Navigation Editor.
The Navigation component is designed to help you implement Google’s new recommended app structure, where a single Activity “hosts” the Navigation Graph, and all your destinations are implemented as fragments. In this article, we’ll follow this recommended approach and creating an application that consists of a MainActivity and three fragment destinations.
However, the Navigation component isn’t just for applications that have this recommended structure. A project can have multiple Navigation Graphs, and you can use fragments and Activities as destinations within those Navigation Graphs. If you’re migrating a large, mature project to the Navigation component, you may find it easier to separate your app’s navigational flows into groups, where each group consists of a “main” Activity, some related fragments, and its own Navigation Graph.
Adding the Navigation Editor to Android Studio
To help you get the most out of the Navigation component, Android Studio 3.2 Canary and higher features a new Navigation Editor.
To enable this editor:
- Select “Android Studio > Preferences…” from the Android Studio menu bar.
- In the left-hand menu, choose “Experimental.”
- If it isn’t already selected, select the “Enable Navigation Editor” checkbox.
- Click “OK.”
- Restart Android Studio.
Project dependencies: Navigation Fragment and Navigation UI
Create a new project with the settings of your choice, then open its build.gradle file and add navigation-fragment and navigation-ui as project dependencies:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
//Add the following//
implementation "android.arch.navigation:navigation-fragment:1.0.0-alpha05"
//Navigation-UI provides access to some helper functions//
implementation "android.arch.navigation:navigation-ui:1.0.0-alpha05"
implementation 'com.android.support:support-v4:28.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
Get a visual overview of your app’s navigation
To create a Navigation Graph:
- Control-click your project’s “res” directory and select “New > Android Resource Directory.”
- Open the “Resource type” dropdown and choose “navigation.”
- Select “OK.”
- Control-click your new “res/navigation” directory and select “New > Navigation resource file.”
- Open the “Resource type” dropdown and select “Navigation.”
- Give this file name; I’m using “nav_graph.”
- Click “OK.”
Open your “res/navigation/nav_graph” file, and the Navigation Editor will launch automatically. Similar to the layout editor, the Navigation Editor is split into “Design” and “Text” tabs.
If you select the “Text” tab, you’ll see the following XML:
<?xml version="1.0" encoding="utf-8"?>
//'Navigation’ is the root node of every navigation graph//
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/nav_graph">
</navigation>
The “Design” tab is where you can build and edit your app’s navigation visually.
From left-to-right, the Navigation Editor consists of:
- A Destinations list: This lists all the destinations that make up this particular Navigation Graph, plus the Activity where the Navigation Graph is hosted.
- The Graph Editor: The Graph Editor provides a visual overview of all the graph’s destinations and the actions connecting them.
- The Attributes Editor: If you select a destination or an action in the Graph Editor, the “Attributes” panel will display information about the currently-selected item.
Populating the Navigation Graph: Adding destinations
Our Navigation Graph is currently empty. Let’s add some destinations.
You can add Activities or fragments that already exist, but you can also use the Navigation Graph to quickly and easily create new fragments:
- Give the “New Destination” button a click, and select “Create blank destination.”
- In the “Fragment Name” field, enter your fragment’s class name; I’m using “FirstFragment.”
- Make sure the “Create layout XML” checkbox is selected.
- Complete the “Fragment Layout Name” field; I’m using “fragment_first.”
- Click “Finish.”
A FirstFragment subclass and corresponding “fragment_first.xml” layout resource file will now be added to your project. FirstFragment will also appear as a destination in the Navigation Graph.
If you select FirstFragment in the Navigation Editor, then the “Attributes” panel will display some information about this destination, such as its class name and the ID you’ll use to reference this destination elsewhere in your code.
Rinse and repeat to add a SecondFragment and ThirdFragment to your project.
Switch to the “Text” tab and you’ll see that the XML has been updated to reflect these 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/firstFragment">
<fragment
android:id="@+id/firstFragment"
android:name="com.jessicathornsby.navigationexample.FirstFragment"
android:label="fragment_first"
tools:layout="@layout/fragment_first" />
<fragment
android:id="@+id/secondFragment"
android:name="com.jessicathornsby.navigationexample.SecondFragment"
android:label="fragment_second"
tools:layout="@layout/fragment_second" />
<fragment
android:id="@+id/thirdFragment"
android:name="com.jessicathornsby.navigationexample.ThirdFragment"
android:label="fragment_third"
tools:layout="@layout/fragment_third" />
</navigation>
Every Navigation Graph has a start destination, which is the screen that’s displayed when the user launches your app. In the above code, we’re using FirstFragment as our app’s start destination. If you switch to the “Design” tab, then you’ll notice a house icon, which also marks FirstFragment as the graph’s start destination.
If you’d prefer to use a different starting point, then select the Activity or fragment in question, and then select “Set Start Destination” from the “Attributes” panel.
Alternatively, you can make this change at the code level:
<?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"
//SecondFragment is now the start destination//
app:startDestination="@id/secondFragment">
Updating your fragment layouts
Now we have our destinations, let’s add some user interface elements so that it’s always clear which fragment we’re currently viewing.
I’m going to add the following to each fragment:
- A TextView that contains the fragment’s title
- A button that’ll allow the user to navigate from one fragment to the next
Here’s the code for each layout resource file:
Fragment_first.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=".FirstFragment">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fragment 1" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Navigate to 2nd fragment" />
</LinearLayout>
</FrameLayout>
Fragment_second.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=".SecondFragment">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fragment 2" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Navigate to 3rd fragment" />
</LinearLayout>
</FrameLayout>
Fragment_third.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=".ThirdFragment">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fragment 3" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Back to the 1st fragment" />
</LinearLayout>
</FrameLayout>
Connecting your destinations with actions
The next step is linking our destinations via actions.
You can create an action in the Navigation Editor using simple drag and drop:
- Make sure the Editor’s “Design” tab is selected.
- Hover over the right side of the destination that you want to navigate from, which in this instance is FirstFragment. A circle should appear.
- Click and drag your cursor to the destination that you want to navigate to, which is SecondFragment. A blue line should appear. When SecondFragment is highlighted blue, release the cursor to create a link between these destinations.
There should now be an action arrow linking FirstFragment to SecondFragment. Click to select this arrow, and the “Attribute” panel will update to display some information about this action, including its system-assigned ID.
This change is also reflected in the Navigation Graph’s 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/nav_graph"
app:startDestination="@id/firstFragment">
<fragment
android:id="@+id/firstFragment"
android:name="com.jessicathornsby.navigationexample.FirstFragment"
android:label="fragment_first"
tools:layout="@layout/fragment_first" >
<action
//Use this ID to reference the action elsewhere in your code//
android:id="@+id/action_firstFragment_to_secondFragment"
app:destination="@id/secondFragment" />
</fragment>
…
…
…
Rinse and repeat to create an action linking SecondFragment to ThirdFragment and an action linking ThirdFragment to FirstFragment.
Hosting the Navigation Graph
The Navigation Graph provides a visual representation of your app’s destinations and actions, but invoking these actions requires some additional code.
Once you’ve created a Navigation Graph, you need to host it inside an Activity by adding a NavHostFragment to that Activity’s layout file. This NavHostFragment provides a container where the navigation can occur and will also be responsible for swapping fragments in and out as the user navigates around your app.
Open your project’s “activity_main.xml” file and add a NavHostFragment.
<?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">
//Create a fragment that’ll act as the NavHostFragment//
<fragment
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:id="@+id/my_nav_host_fragment"
//Mark this fragment as the NavHostFragment//
android:name="androidx.navigation.fragment.NavHostFragment"
//Connect the NavHostFragment to your Navigation Graph//
app:navGraph="@navigation/nav_graph"
//Connect the back button to the NavHostFragment//
app:defaultNavHost="true"/>
</android.support.constraint.ConstraintLayout>
In the above code, app:defaultNavHost=”true” allows the Navigation Host to intercept whenever the system’s “Back” button is pressed, so the app always honours the navigation described in your Navigation Graph.
Triggering transitions with NavController
Next, we need to implement a NavController, which is a new component that’s responsible for managing the process of navigation within a NavHostFragment.
To navigate to a new screen, you need to retrieve a NavController using Navigation.findNavController, call the navigate() method, then pass either the ID of the destination that you’re navigating to or the action that you want to invoke. For example, I’m invoking “action_firstFragment_to_secondFragment,” which will transport the user from FirstFragment, to SecondFragment:
NavController navController = Navigation.findNavController(getActivity(), R.id.my_nav_host_fragment);
navController.navigate(R.id.action_firstFragment_to_secondFragment);
The user will move to a new screen by clicking a button, so we also need to implement an OnClickListener.
After making these changes, FirstFragment should look something like this:
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
public class FirstFragment extends Fragment {
public FirstFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_first, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
Button button = (Button) view.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
NavController navController = Navigation.findNavController(getActivity(), R.id.my_nav_host_fragment);
navController.navigate(R.id.action_firstFragment_to_secondFragment);
}
});
}
}
Next, open your MainActivity and add the following:
- NavigationView.OnNavigationItemSelectedListener: A listener for handling events on navigation items
- SecondFragment.OnFragmentInteractionListener: An interface that was generated when you created SecondFragment via the Navigation Editor
MainActivity also needs to implement the onFragmentInteraction() method, which allows communication between the fragment and the Activity.
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.net.Uri;
import android.view.MenuItem;
import android.support.design.widget.NavigationView;
import android.support.annotation.NonNull;
public class MainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener, SecondFragment.OnFragmentInteractionListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
return false;
}
@Override
public void onFragmentInteraction(Uri uri) {
}
}
Adding more navigation
To implement the rest of our app’s navigation, we just need to copy/paste the onViewCreated block and make a few tweaks so that we’re referencing the correct button widgets and navigation actions.
Open your SecondFragment and add the following:
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
Button button = (Button) view.findViewById(R.id.button2);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
NavController navController = Navigation.findNavController(getActivity(), R.id.my_nav_host_fragment);
navController.navigate(R.id.action_secondFragment_to_thirdFragment);
}
});
}
Then, update ThirdFragment’s onViewCreated block:
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
Button button = (Button) view.findViewById(R.id.button3);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
NavController navController = Navigation.findNavController(getActivity(), R.id.my_nav_host_fragment);
navController.navigate(R.id.action_thirdFragment_to_firstFragment);
}
});
}
Finally, don’t forget to add the ThirdFragment.OnFragmentInteractionListener interface to your MainActivity:
public class MainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener, SecondFragment.OnFragmentInteractionListener, ThirdFragment.OnFragmentInteractionListener {
Run this project on your Android device or Android Virtual Device (AVD) and test the navigation. You should be able to navigate between all three fragments by clicking the different buttons.
Creating custom transition animations
At this point, the user can move around your app, but the transition between each fragment is pretty abrupt. In this final section, we’ll use the Navigation component to add a different animation to each transition, so they happen more smoothly.
Every animation that you want to use must be defined in its own animation resource file, inside a “res/anim” directory. If your project doesn’t already contain a “res/anim” directory, you’ll need to create one:
- Control-click your project’s “res” folder and select “New > Android Resource Directory.”
- Give this Directory the name “anim.”
- Open the “Resource type” dropdown, and choose “anim.”
- Click “OK.”
Let’s start by defining a fade-out animation:
- Control-click your project’s “res/anim” directory.
- Select “New > Animation resource file.”
- Give this file the name “fade_out.”
- Open your “fade_out” file, and add the following:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="@android:integer/config_mediumAnimTime"
android:fromAlpha="1.0"
android:toAlpha="0.0"/>
</set>
Repeat the above steps to create a second animation resource file, named “slide_out_left,” then add the following:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="0%" android:toXDelta="-100%"
android:fromYDelta="0%" android:toYDelta="0%"
android:duration="700"/>
</set>
Create a third file, named “slide_out_right” and add the following:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="0%" android:toXDelta="100%"
android:fromYDelta="0%" android:toYDelta="0%"
android:duration="700"/>
</set>
You can now assign these animations to your actions via the Navigation Editor. To play the fade-out animation whenever the user navigates from FirstFragment to SecondFragment:
- Open your Navigation Graph and make sure the “Design” tab is selected.
- Click to select the action that links FirstFragment to SecondFragment.
- In the “Attributes” panel, click to expand the “Transitions” section. By default, every dropdown in this section should be set to “None.”
- Open the “Enter” dropdown, which controls the animation that plays whenever SecondFragment transitions to the top of the back stack. Select the “fade_out” animation.
If you switch to the “Design” tab, then you’ll see that this animation has been added to “action_firstFragment_to_secondFragment.”
<?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/firstFragment">
<fragment
android:id="@+id/firstFragment"
android:name="com.jessicathornsby.navigationexample.FirstFragment"
android:label="fragment_first"
tools:layout="@layout/fragment_first" >
<action
android:id="@+id/action_firstFragment_to_secondFragment"
app:destination="@id/secondFragment"
app:enterAnim="@anim/fade_out" />
</fragment>
Run the updated project on your Android device or AVD. You should now encounter a fade-out effect whenever you navigate from FirstFragment to SecondFragment.
If you take another look at the “Attributes” panel, you’ll see that “Enter” isn’t the only part of the transition where you can apply an animation. You can also choose from:
- Exit: The animation that plays when a fragment is leaving the stack
- Pop Enter: The animation that plays when a fragment is populating the top of the stack
- Pop Exit: The animation that plays when a fragment is transitioning to the bottom of the stack
Try experimenting by applying different animations to different parts of your transitions. You can also download the completed project from GitHub.
Wrapping up
In this article, we looked at how you can use the Navigation Architecture component to create a single-Activity, multiple-fragment application, complete with custom transition animations. Has the Navigation component convinced you to migrate your projects to this kind of application structure? Let us know in the comments below!