An Android Navigation Component Tutorial

Revision as of 19:39, 11 January 2019 by Neil (Talk | contribs)

Revision as of 19:39, 11 January 2019 by Neil (Talk | contribs)

PreviousTable of ContentsNext
An Overview of the Android Jetpack Navigation Architecture ComponentCreating and Managing Overflow Menus on Android


You are reading a sample chapter from the Android Studio 3.2 Edition of this book.

Purchase the fully updated Android Studio Hedgehog Edition of this publication in eBook ($32.99) or Print ($49.99) format

Android Studio Hedgehog Essentials - Java Edition Print and eBook (PDF) editions contain 87 chapters and over 800 pages
Buy Print Preview Book


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.

Creating the NavigationDemo Project

Begin by launching Android Studio and creating a new project, entering NavigationDemo into the Application name field and ebookfrenzy.com as the Company Domain setting before clicking on the Next button.

On the form factors screen, enable the Phone and Tablet option and set the minimum SDK setting to API 26: Android 8.0 (Oreo). Continue through the setup screens, requesting the Fragment + ViewModel option and using the default activity, fragment and view model names.

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:

https://developer.android.com/topic/libraries/architecture/adding-components#navigation

After adding the navigation dependencies to the file, click on the Sync Now link to resynchronize the build configuration for the project.


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:


As 3.2 navigation new resource menu.png


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.


As 3.2 navigation new resource file.png


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:


As 3.2 navigation no hosts.png


Before adding any destinations to the navigation graph, the next step is to add a navigation host fragment to the project.

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:


As 3.2 navigation add navhost.png


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):


As 3.2 navigation navhost added.png


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:


As 3.2 navigation add destination.png 

Select main_fragment as the destination so that is appears within the navigation graph:

As 3.2 navigation destination added.png


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:


As 3.2 navigation second destination.png

You are reading a sample chapter from the Android Studio 3.2 Edition of this book.

Purchase the fully updated Android Studio Hedgehog Edition of this publication in eBook ($32.99) or Print ($49.99) format

Android Studio Hedgehog Essentials - Java Edition Print and eBook (PDF) editions contain 87 chapters and over 800 pages
Buy Print Preview Book

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:


As 3.2 navigation main layout.png


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:


As 3.2 navigation convert framelayout.png


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.

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:


As 3.2 navigation action circle.png


Click within the circle and drag the resulting line to the secondFragment destination:


As 3.2 navigation action circle connect.png


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:


As 3.2 navigation action arrow.png


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" />

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) {
    }
}

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))

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:


As 3.2 navigation add argument.png


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:


As 3.2 navigation argument added.png


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.

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.


You are reading a sample chapter from the Android Studio 3.2 Edition of this book.

Purchase the fully updated Android Studio Hedgehog Edition of this publication in eBook ($32.99) or Print ($49.99) format

Android Studio Hedgehog Essentials - Java Edition Print and eBook (PDF) editions contain 87 chapters and over 800 pages
Buy Print Preview Book



PreviousTable of ContentsNext
An Overview of the Android Jetpack Navigation Architecture ComponentCreating and Managing Overflow Menus on Android