Very few Android apps today consist of just a single screen. In reality, most apps comprise multiple screens through which the user navigates using screen gestures, button clicks, and menu selections. Before the introduction of Android Jetpack, implementing navigation within an app was largely a manual coding process with no easy way to view and organize potentially complex navigation paths. However, this situation has improved considerably with the introduction of the Android Navigation Architecture Component combined with support for navigation graphs in Android Studio.
Every app has a home screen that appears after the app has launched and after any splash screen has appeared (a splash screen being the app branding screen that appears temporarily while the app loads). The user will typically perform tasks from this home screen, resulting in other screens appearing. These screens will usually take the form of other activities and fragments within the app. For example, a messaging app may have a home screen listing current messages from which users can navigate to another screen to access a contact list or a settings screen. The contacts list screen, in turn, might allow the user to navigate to other screens where new users can be added or existing contacts updated. Graphically, the app’s navigation graph might be represented as shown in Figure 47-1:
Each screen that makes up an app, including the home screen, is referred to as a destination and is usually a fragment or activity. The Android navigation architecture uses a navigation stack to track the user’s path through the destinations within the app. When the app first launches, the home screen is the first destination placed onto the stack and becomes the current destination. When the user navigates to another destination, that screen becomes the current destination and is pushed onto the stack above the home destination. As the user navigates to other screens, they are also pushed onto the stack. Figure 47-2, for example, shows the current state of the navigation stack for the hypothetical messaging app after the user has launched the app and is navigating to the “Add Contact” screen:
As the user navigates back through the screens using the system back button, each destination is popped off the stack until the home screen is once again the only destination on the stack. In Figure 47-3, the user has navigated back from the Add Contact screen, popping it off the stack and making the Contacts List screen the current destination:
All of the work involved in navigating between destinations and managing the navigation stack is handled by a navigation controller, represented by the NavController class.
Adding navigation to an Android project using the Navigation Architecture Component is a straightforward process involving a navigation host, navigation graph, navigation actions, and minimal code writing to obtain a reference to, and interact with, the navigation controller instance.
Declaring a Navigation Host
A navigation host is a special fragment (NavHostFragment) embedded into the user interface layout of an activity and serves as a placeholder for the destinations through which the user will navigate. Figure 47-4, for example, shows a typical activity screen and highlights the area represented by the navigation host fragment:
A NavHostFragment can be placed into an activity layout within the Android Studio layout editor either by dragging and dropping an instance from the Containers section of the palette or by manually editing the XML as follows:
<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" > <androidx.fragment.app.FragmentContainerView 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>Code language: HTML, XML (xml)
The points of note in the above navigation host fragment element are the reference to the NavHostFragment in the name property, the setting of defaultNavHost to true, and the assignment of the file containing the navigation graph to the navGraph property.
When the activity launches, this navigation host fragment is replaced by the home destination designated in the navigation graph. As the user navigates through the app screens, the host fragment will be replaced by the appropriate fragment for the destination.
The Navigation Graph
A navigation graph is an XML file that contains the destinations that will be included in the app navigation. In addition to these destinations, the file contains navigation actions that define navigation between destinations and optional arguments for passing data from one destination to another. Android Studio includes a navigation graph editor that can be used to design graphs and implement actions either visually or by manually editing the XML.
Figure 47-5 shows the Android Studio navigation graph editor in Design mode:
The destinations list (A) lists all destinations within the graph. Selecting a destination from the list will locate and select the corresponding destination in the graph (particularly useful for locating specific destinations in a large graph). The navigation graph panel (B) contains a dialog for each destination representing the user interface layout. In this example, this graph contains two destinations named mainFragment and secondFragment. Arrows between destinations (C) represent navigation action connections. Actions are added by hovering the mouse pointer over the edge of the origin until a circle appears, then clicking and dragging from the circle to the destination. The Attributes panel (D) allows the properties of the currently selected destination or action connection to be viewed and modified. In the above figure, the attributes for the action are displayed. New destinations are added by clicking on the button marked E and selecting options from a menu. Options are available to add existing fragments or activities as destinations or to create new blank fragment destinations. The Component Tree panel (F) provides a hierarchical overview of the navigation graph.
The underlying XML for the navigation graph can be viewed and modified by switching the editor into Code mode. The following XML listing represents the navigation graph for the destinations and action connection shown in Figure 47-5 above:
<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="fragment_main" tools:layout="@layout/fragment_main" > <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" > </fragment> </navigation>Code language: HTML, XML (xml)
Navigation graphs can also be split over multiple files to improve organization and promote reuse. When structured in this way, nested graphs are embedded into root graphs. To create a nested graph, shift-click on the destinations to be nested, right-click over the first destination and select the Move to Nested Graph -> New Graph menu option. The nested graph will then appear as a new node in the graph. Double-click on the nested graph node to load the graph file into the editor to access the nested graph.
Accessing the Navigation Controller
Navigating from one destination to another usually occurs in response to an event within an app, such as a button click or menu selection. Before a navigation action can be triggered, the code must first obtain a reference to the navigation controller instance. This requires a call to the findNavController() method of the Navigation or NavHostFragment classes. The following code, for example, can be used to access the navigation controller of an activity. Note that for the code to work, the activity must contain a navigation host fragment:
NavController controller = Navigation.findNavController(activity, R.id.demo_nav_host_fragment);Code language: Java (java)
In this case, the method call is passed a reference to the activity and the id of the NavHostFragment embedded in the activity’s layout.
Alternatively, the navigation controller associated with any view may be identified by passing that view to the method:
NavController controller = Navigation.findNavController(binding.button);Code language: Java (java)
The final option finds the navigation controller for a fragment by calling the findNavController() method of the NavHostFragment class, passing through a reference to the fragment:
NavController controller = NavHostFragment.findNavController(fragment);Code language: Java (java)
Triggering a Navigation Action
Once the navigation controller has been found, a navigation action is triggered by calling the controller’s navigate() method and passing through the resource id of the action to be performed. For example:
controller.navigate(R.id.goToContactsList);Code language: Java (java)
The id of the action is defined within the Attributes panel of the navigation graph editor when an action connection is selected.
Data may be passed from one destination to another during a navigation action by using arguments declared within the navigation graph file. An argument consists of a name, type, and an optional default value and may be added manually within the XML or using the Attributes panel when an action arrow or destination is selected within the graph. In Figure 47-6, for example, an integer argument named contactsCount has been declared with a default value of 0:
Once added, arguments are placed within the XML element of the receiving destination, for example:
<fragment android:id="@+id/secondFragment" android:name="com.ebookfrenzy.navigationdemo.SecondFragment" android:label="fragment_second" tools:layout="@layout/fragment_second" > <argument android:name="contactsCount" android:defaultValue=0 app:type="integer" /> </fragment>Code language: HTML, XML (xml)
The Navigation Architecture Component provides two techniques for passing data between destinations. One approach involves placing the data into a Bundle object that is passed to the destination during an action, where it is then unbundled and the arguments extracted.
The main drawback to this particular approach is that it is not “type safe”. In other words, if the receiving destination treats an argument as a different type than it was declared (for example, treating a string as an integer), this error will not be caught by the compiler and will likely cause problems at runtime.
A better option, which is used in this book, is safeargs. Safeargs is a plugin for the Android Studio Gradle build system which automatically generates special classes that allow arguments to be passed in a type-safe way. The safeargs approach to argument passing will be described and demonstrated in the next chapter (“An Android Jetpack Navigation Component Tutorial”).
Navigation within the context of an Android app user interface refers to the ability of a user to move back and forth between different screens. Once time-consuming to implement and difficult to organize, Android Studio and the Navigation Architecture Component now make it easier to implement and manage navigation within Android app projects.
The different screens within an app are referred to as destinations and are usually represented by fragments or activities. All apps have a home destination, including the screen displayed when the app first loads. The content area of this layout is replaced by a navigation host fragment which is swapped out for other destination fragments as the user navigates the app. The navigation path is defined by the navigation graph file consisting of destinations and the actions that connect them together with any arguments to be passed between destinations. Navigation is handled by navigation controllers, which, in addition to managing the navigation stack, provide methods to initiate navigation actions from within app code.