39.
An Android Jetpack
Navigation Component Tutorial
The previous chapter described the Android Jetpack Navigation Component and how it integrates with the navigation graphing features of Android Studio to provide an easy way to implement navigation between the screens of an Android app. In this chapter a new Android Studio project will be created that makes use of these navigation features to implement a simple app containing multiple screens. In addition to demonstrating the use of the Android Studio navigation graph editor, the example project will also implement the passing of data between origin and destination screens using type-safe arguments.
39.1
Creating the NavigationDemo Project
Select the Start a new Android Studio project
quick start option from the welcome screen and, within the resulting new project dialog, choose the Add No Activity template before clicking on the Next button.
Enter NavigationDemo
into the Name field and specify com.ebookfrenzy.navigationdemo
as the package name. Before clicking on the Finish button, change the Minimum API level setting to API 26: Android 8.0 (Oreo) and the Language menu to Java.
Within the Project tool window, locate the app -> java -> com.ebookfrenzy. navigationdemo
entry, right-click on it and select the New -> Activity -> Fragment + ViewModel
option from the resulting menu. In the New Android Activity dialog, enable the Launcher Activity option, change the source language to Java and click on the Finish button to create the activity class with the remaining fields set to the default values.
39.2
Adding Navigation to the Build Configuration
A new Android Studio project does not, by default, include the Navigation component libraries in the build configuration files. Before performing any other tasks, therefore, the first step is to modify the app level build.gradle
file. Locate this file in the project tool window (Gradle Scripts -> build.gradle (Module: app)
), double-click on it to load it into the code editor and modify the dependencies section to add the navigation libraries:
dependencies {
implementation "android.arch.navigation:navigation-fragment:1.0.0-alpha07"
implementation "android.arch.navigation:navigation-ui:1.0.0-alpha07"
.
.
}
Note that newer versions of these libraries may have been released since this book was published. To find the latest version, refer to the following URL:
After adding the navigation dependencies to the file, click on the Sync Now
link to resynchronize the build configuration for the project.
39.3
Creating the
Navigation Graph Resource File
With the navigation libraries added to the build configuration the navigation graph resource file can now be added to the project. As outlined in
“An Overview of the Navigation Architecture Component”
, this is an XML file that contains the fragments and activities through which the user will be able to navigate, together with the actions to perform the transitions and any data to be passed between destinations.
Within the Project tool window, locate the res
folder (app -> res
), right-click on it and select the New ->Android Resource File
menu option:
Figure 39-1
After the menu item has been selected, the New Resource File dialog will appear. In this dialog, name the file
navigation_graph
and change the Resource type menu to Navigation as outlined in
Figure 39-2
before clicking on the OK button to create the file. If the Navigation type is not available, select the
File -> Settings
menu option (
Android Studio -> Preferences...
on macOS), display the
Experimental
screen and turn on the
Enable Navigation Editor
option.
Figure 39-
2
After the navigation graph resource file has been added to the project it will appear in the main panel ready for new destinations to be added. Switch the editor to Text mode and review the XML for the graph before any destinations are added:
<?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"
android:id="@+id/navigation_graph">
</navigation>
Switch back to Design mode within the editor and note that the Host section of the Destinations panel indicates that no navigation host fragments have been detected within the project:
Figure 39-3
Before adding any destinations to the navigation graph, the next step is to add a navigation host fragment to the project.
39.4
Declaring a
Navigation Host
For this project, the navigation host fragment will be contained within the user interface layout of the main activity. This means that the destination fragments within the navigation graph will appear in the content area of the main activity currently occupied by the main_fragment.xml
layout. Locate the main activity layout file in the Project tool window (app -> res -> layout -> main_activity.xml
) and load it into the layout editor tool.
With the layout editor in Design mode, drag a NavHostFragment element from the Containers section of the Palette and drop it onto the container area of the activity layout as indicated by the arrow in
Figure 39-4
:
Figure 39-
4
From the resulting Navigation Graphs dialog, select the navigation_graph.xml
file created in the previous section and click on the OK button.
With the newly added NavHostFragment instance selected in the layout, use the Attributes tool window to change the ID of the element to demo_nav_host_fragment
.
Switch the layout editor to Text mode and review the XML file. Note that the editor has correctly configured the navigation graph property to reference the navigation_graph.xml
file and that the defaultNavHost
property has been set to true
:
<?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:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<fragment
android:id="@+id/demo_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_graph" />
</FrameLayout>
With the NavHostFragment configured within the main activity layout file, some code needs to be removed from the MainActivity.java
class file to prevent the activity from loading the main_fragment.xml
file at runtime. Load this file into the code editor, locate the onCreate()
method and remove the code responsible for displaying the main fragment:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, MainFragment.newInstance())
.commitNow();
}
}
Return to the navigation_graph.xml
file and confirm that the NavHostFragment instance has been detected (it may be necessary to close and reopen the file before the change appears):
Figure 39-5
39.5
Adding
Navigation Destinations
Remaining in the navigation graph it is now time to add the first destination. Since the project already has a fragment for the first screen (
main_fragment.xml
) this will be the first destination to be added to the graph. Click on the new destination button highlighted in
Figure 39-6
to select or create a destination:
Figure 39-
6
Select main_fragment
as the destination so that it appears within the navigation graph:
Figure 39-7
The home icon positioned above the destination node indicates that this is the start destination
. This means that the destination will be the first displayed when the activity containing the NavHostFragment is created. To change the start destination to another destination, select that node in the graph and click on the Set Start Destination
button located in the Attributes tool window.
Review the XML content of the navigation graph by switching the editor to Text mode:
<?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/mainFragment">
<fragment
android:id="@+id/mainFragment"
android:name="com.ebookfrenzy.navigationdemo.ui.main.MainFragment"
android:label="main_fragment"
tools:layout="@layout/main_fragment" />
</navigation>
Before any navigation can be performed, the graph needs at least one more destination. This time, the navigation graph editor will be used to create a new blank destination. Switch back to Design mode and click once again on the
New Destination
button, this time selecting the
Create blank destination
option from the menu. In the resulting dialog, name the new fragment
SecondFragment
and the layout
fragment_second
before clicking on the Finish button. After a short delay while the project rebuilds, the new fragment will appear as another destination within the graph as shown in
Figure 39-8
:
Figure 39-
8
39.6
Designing the Destination Fragment Layouts
Before adding actions to navigate between destinations now is a good time to add some user interface components to the two destination fragments in the graph. Begin by double-clicking on the mainFragment destination so that the
main_fragment.xml
file loads into the layout editor. Select and delete the current TextView widget, then drag and drop Button and Plain Text EditText widgets onto the layout so that it resembles that shown in
Figure 39-9
below:
Figure 39-
9
Once the views are correctly positioned, click on the Infer constraints
button in the toolbar to add any missing constraints to the layout. Select the EditText view and use the Attributes tool window to delete the default “Name” text and to change the ID of the widget to userText
.
Return to the navigation_graph.xml
file and double-click on the secondFragment destination to load the fragment_second.xml
file into the layout editor, then select and delete the default TextView instance. Within the Component Tree panel, right-click on the FrameLayout entry and select the Convert from FrameLayout to ConstraintLayout...
menu option, accepting the default settings in the resulting conversion dialog:
Figure 39-10
With the fragment’s parent layout manager now converted to the more flexible ConstraintLayout, drag and drop a new TextView widget so that it is positioned in the center of the layout and click on the Infer constraints
button to add any missing constraints. With the new TextView selected, use the Attributes panel to change the ID to argText
.
39.7
Adding an Action to the
Navigation Graph
Now that the two destinations have been added to the graph and the corresponding user interface layouts designed, the project now needs a way for the user to navigate from the main fragment to the second fragment. This will be achieved by adding an action to the graph which can then be referenced from within the app code.
To establish an action connection with the main fragment as the origin and second fragment as the destination, open the navigation graph and hover the mouse pointer over the vertical center of the right-hand edge of the mainFragment destination so that a circle appears as highlighted in
Figure 39-11
:
Figure 39-1
1
Click within the circle and drag the resulting line to the secondFragment destination:
Figure 39-12
Release the line to establish the action connection between the origin and destination at which point the line will change into an arrow as shown in
Figure 39-13
:
Figure 39-1
3
An action connection may be deleted at any time by selecting it and pressing the keyboard Delete key. With the arrow selected, review the properties available within the Attributes tool window and change the ID to mainToSecond
. This is the ID by which the action will be referenced within the code. Switch the editor to Text mode and note that the action is now included within the 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_graph"
app:startDestination="@id/mainFragment">
<fragment
android:id="@+id/mainFragment"
android:name="com.ebookfrenzy.navigationdemo.ui.main.MainFragment"
android:label="main_fragment"
tools:layout="@layout/main_fragment" >
<action
android:id="@+id/mainToSecond"
app:destination="@id/secondFragment" />
</fragment>
<fragment
android:id="@+id/secondFragment"
android:name="com.ebookfrenzy.navigationdemo.SecondFragment"
android:label="fragment_second"
tools:layout="@layout/fragment_second" />
39.8
Implement the
OnFragmentInteractionListener
Before adding code to trigger the action, the MainActivity class will need to be modified to implement the OnFragmentInteractionListener interface. This is an interface that was generated within the SecondFragment class when the blank fragment was created within the navigation graph editor. In order to conform to the interface, the activity needs to implement a single method named onFragmentInteraction()
and is used to implement communication between the fragment and the activity.
Edit the MainActivity.java
file and modify it so that it reads as follows:
.
.
import android.net.Uri;
.
.
public class MainActivity extends AppCompatActivity implements SecondFragment.OnFragmentInteractionListener
{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
}
@Override
public void onFragmentInteraction(Uri uri) {
}
}
39.9
Triggering the Action
Now that the action has been added to the navigation graph, the next step is to add some code within the main fragment to trigger the action when the Button widget is clicked. Locate the MainFragment.java
file, load it into the code editor and modify the onActivityCreated()
method to obtain a reference to the button instance and to configure an onClickListener instance to be called when the user clicks the button:
.
.
import android.widget.Button;
import androidx.navigation.Navigation;
.
.
public class MainFragment extends Fragment {
.
.
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
Button button = getView().findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Navigation.findNavController(view).navigate(
R.id.mainToSecond);
}
});
}
}
The above code obtains a reference to the navigation controller and calls the navigate()
method on that instance, passing through the resource ID of the navigation action as an argument.
Compile and run the app and verify that clicking the button in the main fragment transitions to the second fragment.
As an alternative to this approach to setting up a listener, the Navigation class also includes a method named createNavigateOnClickListener()
which provides a more efficient way of setting up a listener and navigating to a destination. The same result can be achieved, therefore, using the following single line of code to initiate the transition:
button.setOnClickListener(Navigation.createNavigateOnClickListener(
R.id.mainToSecond, null))
39.10
Passing Data Using
Safeargs
The next objective in this tutorial is to pass the text entered into the EditText view in the main fragment to the second fragment where it will be displayed on the TextView widget. As outlined in the previous chapter, the Android Navigation component supports two approaches to passing data. This chapter will make use of type safe argument passing.
The first step in using safeargs is to add the safeargs plugin to the Gradle build configuration. Using the Project tool window, locate and edit the project level build.gradle
file (Gradle Scripts -> build.gradle (Project: NavigationDemo)
) to add the plugin to the dependencies as follows (once again keeping in mind that a more recent version may now be available):
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha08"
.
.
Next, edit the app level build.gradle
file (Gradle Scripts -> build.gradle (Module: App)
) to apply the plugin as follows and resync the project when prompted to do so.
apply plugin: 'com.android.application'
apply plugin: 'androidx.navigation.safeargs'
.
.
android {
.
.
The next step is to define any arguments that will be received by the destination which, in this case, is the second fragment. Edit the navigation graph, select the secondFragment destination and locate the Arguments section within the Attributes tool window. Click on the + button (highlighted in
Figure 39-14
) to add a new argument to the destination:
Figure 39-1
4
After the + button has been clicked, a row will appear into which the argument name, type and default value need to be entered. Name the argument message
, set the type to string
and enter No Message
into the default value field:
Figure 39-15
The newly configured argument will appear in the secondFragment element of the navigation_graph.xml
file as follows:
<fragment
android:id="@+id/secondFragment"
android:name="com.ebookfrenzy.navigationdemo.SecondFragment"
android:label="fragment_second"
tools:layout="@layout/fragment_second" >
<argument
android:name="message"
android:defaultValue="No Message"
app:argType="string" />
</fragment>
The next step is to add code to the Mainfragment.java
file to extract the text from the EditText view and pass it to the second fragment during the navigation action. This will involve using some special navigation classes that have been generated automatically by the safeargs plugin. Currently the navigation involves the MainFragment class, the SecondFragment class, a navigation action named mainToSecond
and an argument named message
.
When the project is built, the safeargs plugin will generate the following additional classes that can be used to pass and receive arguments during navigation.
-
MainFragmentDirections
- This class represents the origin for the navigation action (named using the class name of the navigation origin with “Directions” appended to the end) and provides access to the action object.
-
ActionMainToSecond
- The class that represents the action used to perform the transition (named based on the ID assigned to the action within the navigation graph file prefixed with “Action”). This class contains a setter method for each of the arguments configured on the destination. For example, since the second fragment destination contains an argument named message
, the class includes a method named setMessage()
. Once configured, an instance of this class is then passed to the navigate()
method of the navigation controller to navigate to the destination.
-
SecondFragmentArgs
- The class used in the destination fragment to access the arguments passed from the origin (named using the class name of the navigation destination with “Args” appended to the end). This class includes a getter method for each of the arguments passed to the destination (i.e. getMessage()
)
Using these classes, the onClickListener
code within the onActivityCreated()
method of the MainFragment.java
file can be modified as follows to extract the current text from the EditText widget, apply it to the action and initiate the transition to the second fragment:
.
.
import android.widget.EditText;
.
.
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
EditText userText = getView().findViewById(R.id.userText);
MainFragmentDirections.MainToSecond action =
MainFragmentDirections.mainToSecond();
action.setMessage(userText.getText().toString());
Navigation.findNavController(view).navigate(action);
}
});
The above code obtains a reference to the action object, sets the message argument string using the setMessage()
method and then calls the navigate()
method of the navigation controller, passing through the action object.
All that remains is to modify the SecondFragment.java
class file to receive the argument after the navigation has been performed and display it on the TextView widget. For this example, the code to achieve these tasks will be added using an onStart()
lifecycle method. Edit the SecondFragment.java
file and add this method so that it reads as follows:
.
.
import android.widget.TextView;
.
.
@Override
public void onStart() {
super.onStart();
TextView argText = getView().findViewById(R.id.argText);
SecondFragmentArgs args = SecondFragmentArgs.fromBundle(getArguments());
String message = args.getMessage();
argText.setText(message);
}
The code in the above method begins by obtaining a reference to the TextView widget. Next, the fromBundle()
method of the SecondFragmentArgs class is called to extract the SecondFragmentArgs object received from the origin. Since the argument in this example was named message
in the navigation_graph.xml
file, the corresponding getMessage()
method is called on the args object to obtain the string value. This string is then displayed on the TextView widget.
Compile and run the app and enter some text before clicking on the Button widget. When the second fragment destination appears, the TextView should now display the text entered in the main fragment indicating that the data was successfully passed between navigation destinations.
39.11
Summary
This chapter has provided a practical example of how to implement Android app navigation using the Navigation Architecture Component together with the Android Studio navigation graph editor. Topics covered included the creation of a navigation graph containing both existing and new destination fragments, the embedding of a navigation host fragment within an activity layout, writing code to trigger navigation events and the passing of arguments between destinations using the Gradle safeargs plugin.