36

The Navigation Architecture Component Tutorial: Getting Started [FREE]

 5 years ago
source link: https://www.tuicool.com/articles/hit/7jiAnau
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Among other goodies, Android Jetpack comes with the Navigation architecture component, which simplifies the implementation of navigation in Android apps.

In this tutorial, you’ll add navigation to a simple book-searching app and learn about:

NavHost

The Navigation architecture component is still in early development, but it is already proving to be a powerful and handy tool, as you will see for yourself.

Note : This tutorial assumes you know the basics of Android development with Kotlin. If you are new to Kotlin, check out our Kotlin introduction tutorial . If you are new to Android development, check out ourAndroid Tutorials first.

Why Do You Need Yet Another Component?

Navigating between screens — passing data, handling the back stack, implementing deep links, etc. — can be complicated. It also comes with quite a bit of boilerplate. To top it off, you should adhere to the new principles of navigation :

  • The app should have a fixed start destination.
  • A stack is used to represent the navigation state of an app.
  • The Up button never exits your app.
  • Up and Back are equivalent within your app’s task.
  • Deep linking to a destination or navigating to the same destination should yield the same stack.

By using the Navigation architecture component, you provide a consistent and predictable experience to users — hassle and boilerplate free.

Getting Started

To use the Navigation architecture component, you must use Android Studio 3.2 or higher. Install it by following the instructions in our Beginning Android development tutorial . Make sure to install the latest stable release .

Next, download the materials for this tutorial using the Download materials button at the top or bottom of the tutorial.

Open Android Studio and import the starter project with File ▸ Open . Find the location where you extracted the starter project, highlight the root folder of the starter project, and click Open . The sample project is named Bookmans Treasure . Once the project is synced, build and run it.

blank-starter-281x500.png

If you see a blank screen, you’re on the right track.

Adding the Navigation Architecture Component

Open the build.gradle file in the app folder and add the following to the dependencies block:

implementation "android.arch.navigation:navigation-fragment-ktx:1.0.0-alpha05"
implementation "android.arch.navigation:navigation-ui-ktx:1.0.0-alpha05"

Wondering about the -ktx suffix in the library names? It signifies the Android KTX libraries that are super nice to use with Kotlin by making your code both more consise and readable.

Sync the project. Then build and run the app again.

blank-starter-281x500.png

It’s still blank, but hold tight — you are now ready to start working on the screen navigation.

Navigation Graph

A set of destinations and actions compose an app’s navigation graph:

app-navigation-graph-650x340.png

The above represents the navigation graph for the Bookmans Treasure app. Nodes represent the screens and arrows show how you navigate between them.

You’ll add the navigation graph to the app now.

You must first enable the Navigation Editor . Click File ▸ Settings (or Android Studio ▸ Preferences on Mac), choose the Experimental category in the left panel, check Enable Navigation Editor :

enable_nav_editor-650x436.png

Click OK and then restart Android Studio.

Now be sure Android is selected in the Project navigator.

project-explorer-android-442x500.png

Click on the res folder, press command+N (on Mac) or Alt+N (on PC) (or File ▸ New ) and select Android Resource File .

new-res-file-271x500.png

A dialog to create a new resource will pop up. Under File name , enter nav_graph . Under Resource type , select Navigation :

new-res-file-dialog-650x373.png

Click OK .

Android Studio will create a new resource directory called navigation with a new file named nav_graph.xml . This is where your navigation graph will live.

new-res-file-created-650x476.png

Opening nav_graph.xml , you can toggle between the Text editor and the Navigation editor by clicking the tabs at the bottom of the editor window.

nav-graph-editor-empty-toggle-650x492.png

Click on the Text tab to open the XML editor.

Destinations

Each destination represents a screen you can navigate to. By default, the Navigation architecture component includes support for Activities and Fragments. You’ll learn how to add custom types later in this tutorial.

Add the following between the navigation tags:

<fragment
  android:id="@+id/bookSearchFragment"
  android:name="com.raywenderlich.android.bookmanstreasure.ui.booksearch.BookSearchFragment"
  android:label="Book Search Fragment"
  tools:layout="@layout/fragment_book_search">
</fragment>

You might get an error on the tools:layout attribute. To fix it, add tools namespace declaration to the navigation tag:

xmlns:tools="http://schemas.android.com/tools"

You can do this manually or place your cursor on tools:layout and press Alt+Enter (on PC) or command+return (on Mac). You’ll get a Quick action pop-up:

tools-namespace-fix-650x233.png

Choose Create namespace declaration .

This makes BookSearchFragment a destination. Fragment destinations have the following attributes:

  • android:id : A unique resource name for this destination.
  • android:name : A fully qualified class name.
  • android:label : The Fragment title.
  • tools:layout : The Fragment layout, rendered by the Navigation editor.

Build and run the app. Getting fed up with the blank screen yet?

Declaring a Start Destination

Every app needs a starting point. To declare a starting destination in the navigation graph, add the following attribute to the navigation tag:

app:startDestination="@+id/bookSearchFragment"

If the app namespace yields an error, follow the same set of steps you used above for the tools namespace to fix it. Build and run to make sure there are no errors. Seriously — blank screen, again?

basic-suprised-1-500x500.png

You’re missing one more thing: the NavHost interface.

NavHost Interface

The NavHost interface enables destinations to be swapped in and out. The Navigation architecture component provides a default implementation: the NavHostFragment .

Open activity_main.xml layout file located in res/layout . Look for the FrameLayout with ID placeholder and replace it with the following fragment:

<fragment
  android:id="@+id/navHostFragment"
  android:name="androidx.navigation.fragment.NavHostFragment"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  app:navGraph="@navigation/nav_graph"
  app:defaultNavHost="true" />

Note the two new attributes:

  • app:navGraph : Specifies which navigation graph will be associated with the navigation host.
  • app:defaultNavHost : If set to true , the navigation host will intercept the Back button.

To ensure the Back button works properly, you also need to override the onSupportNavigateUp() method in MainActivity.kt . Paste this chunk of code anywhere in the class:

override fun onSupportNavigateUp() = 
  findNavController(this, R.id.navHostFragment).navigateUp()

And add the import statement, if necessary:

import androidx.navigation.Navigation.findNavController

Now, build and run the project. No more blank screens!

book-search-empty-281x500.png

Connecting Destinations

Actions

Open nav_graph.xml and add a new destination for WorkDetailsFragment :

<fragment
  android:id="@+id/workDetailsFragment"
  android:name="com.raywenderlich.android.bookmanstreasure.ui.workdetails.WorkDetailsFragment"
  android:label="work_details_fragment"
  tools:layout="@layout/fragment_work_details">
</fragment>

This will be used to show book details, such as the author name, title, list of editions, etc.

To navigate between destinations, you use actions . Find the fragment with the bookSearchFragment ID, and paste this inside its the fragment tags:

<action
 android:id="@+id/actionBookDetails" 
 app:destination="@id/workDetailsFragment" />

You can now navigate to WorkDetailsFragment . The two attributes:

android:id
app:destination

To ensure the action is triggered, open BookSearchFragment.kt in the ui.booksearch package. Look for the comment:

//TODO implement navigation to Book details

Replace the TODO with:

findNavController().navigate(R.id.actionBookDetails)

At the end of the import section, add the following:

import androidx.navigation.fragment.findNavController

Build and run. Search for a book using a generic word like “after” and tap on one of the results. You’ll get a detail page that is currently blank:

work-details-empty-281x500.png

You’ll learn how to pass arguments between destinations shortly. First, add more destinations using the Navigation editor.

Navigation Editor

So far, you’ve added destinations and actions using XML. Now, you’ll get familiar with the Navigation editor . Open the  nav_graph.xml file and click on the Design tab at the bottom of the editor.

nav-graph-editor-sections-1-650x492.png

There are three sections in the editor:

  1. Destinations list
  2. Graph Editor that shows your navigation graph
  3. Attributes Editor for selected destination or action

Add another destination to the graph. Click on the icon with the green + sign at the top of the graph editor.

nav-graph-editor-add-1-650x492.png

Start typing favorites and select FavoritesFragment from the list.

nav-graph-editor-add-dialog-650x493.png

You can edit the properties of the new destination using the Attributes editor. Set the ID to favoritesFragment .

nav-graph-editor-destination-id-attribute-650x492.png

In the editor, hover your mouse over the dot on the middle of the right side of favoritesFragment . Then, press and hold, and drag the line to the workDetailsFragment .

nav-graph-editor-create-action.gif

This creates a new action, navigating from FavoritesFragment to WorkDetailsFragment . Make sure the action (the line with the arrow) is selected in the editor and set the action ID to actionBookDetails .

nav-graph-editor-action-properties-id-247x500.png

Now, add another destination, BookDetailsFragment , following the same steps as above. Then, create an action that navigates from workDetailFragment to bookDetailsFragment . Set its ID to actionShowEdition . Your nav_graph.xml should look like this:

nav-graph-with-book-details-528x500.png

Open WorkDetailsFragment.kt in the ui.and replace:

//TODO Implement navigation to edition details

With:

findNavController().navigate(R.id.actionShowEdition)

And add the import statement, if necessary:

import androidx.navigation.fragment.findNavController

Build and run the app to make sure there are no errors.

Navigating From a Menu

If you’ve toggled the navigation drawer, you might have noticed the Favorites menu item.

app-fav-option-281x500.png

You can use the Navigation architecture component to handle menu actions as well. Open MainActivity.kt and replace:

//TODO setup Navigation Drawer menu item actions

With:

drawerLayout.navView.setupWithNavController(
  navHostFragment.findNavController())

Add the following at the end of the import list:

import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.setupWithNavController
import kotlinx.android.synthetic.main.activity_main.view.*

The Navigation controller assumes the menu IDs are the same as your destination IDs and therefore knows what to do when an item is selected.

Build and run the app. Open the navigation drawer and tap on Favorites :

app-favorites-281x500.png

How easy was that? You can return back to the Book Search using either the back button or the navigation drawer.

Passing Data and Animating Actions

Passing Data Between Destinations

You often need to pass data between your destinations. Remember @+id/actionBookDetails you added earlier?

Currently, it opens the correct screen but shows no details for a selected book. You'll fix this, now.

Go back to the BookSearchFragment and find the call to findNavController().navigate(R.id.actionBookDetails) . Replace it with:

findNavController().navigate(
  R.id.actionBookDetails,
  WorkDetailsViewModel.createArguments(it)
)

Using a helper method, you passed a Bundle instance as a second argument. Its content will be used to construct the destination — the WorkDetailsFragment . You might need to add the following import:

import com.raywenderlich.android.bookmanstreasure.ui.workdetails.WorkDetailsViewModel

Note : The it argument is the data class Book , containing general book information. it is the implicit name within a lambda expression with only one parameter. Check the documentation to learn more.

Build and run the app. Search for your favorite book and tap on a search result:

app-work-details-281x500.png

Now, follow the same set of steps in WorkDetailsFragment . Find:

findNavController().navigate(R.id.actionShowEdition)

Replace it with:

findNavController().navigate(
  R.id.actionShowEdition,
  BookDetailsViewModel.createArguments(it)
)

Add the import if you need to:

import com.raywenderlich.android.bookmanstreasure.ui.bookdetails.BookDetailsViewModel

Build and run the app. Search for a book, check its details and select an edition. You should see the edition details.

app-book-details-281x500.png

Finally, take care of the FavoritesFragment . Look for:

//TODO implement navigation to Work details

Replace it with:

findNavController().navigate(
  R.id.actionBookDetails,
  WorkDetailsViewModel.createArguments(it)
)

As before, you might need to add the import statements:

import androidx.navigation.fragment.findNavController
import com.raywenderlich.android.bookmanstreasure.ui.workdetails.WorkDetailsViewModel

Adding Transitions

You can add custom animations to actions using attributes in the action tag. Open up nav_graph.xml . Select the Text tab at the bottom, and add the following attributes to the actions withg ID @+id/actionBookDetails (there are two such actions):

app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"

Each of the new tags defines a custom transition for one of the cases:

enterAnim
exitAnim
popEnterAnim
popExitAnim

Build and run the app. Search for a book, click on one of the results and see the animation in action.

Types of Destinations

The Navigation architecture component supports Activities and Fragments out-of-the-box, but you can also add custom types.

Adding New Destination Types

Check the com.raywenderlich.android.bookmanstreasure.ui.authordetails package. The AuthorDetailsDialog class can show the author details. You'll put it to use and add support for DialogFragment destinations.

To add a custom destination, you have to implement the Navigator interface. You pass it to the NavigationController when initializing the navigation graph.

First, create a new package at the root level called destinations , by clicking on the com.raywenderlich.android.bookmanstreasure package in your project explorer, then press command+N (or File ▸ New ) and select Package .

new-package-264x500.png

Type "destinations" in the dialog and click OK .

destinations-package-dialog-650x198.png

Click on the new package then press command+N (or File ▸ New ) and select Kotlin File/Class .

new-destination-file-264x500.png

Under name, enter AuthorDetailsNavigator and click OK .

author-navigator-name-1-650x242.png

Add the following code in the new file:

import android.os.Bundle
import androidx.navigation.NavDestination
import androidx.navigation.NavOptions
import androidx.navigation.Navigator

// 1
@Navigator.Name("author")
class AuthorDetailsNavigator : Navigator<AuthorDetailsNavigator.Destination>() {

  // 2
  override fun navigate(destination: Destination, args: Bundle?, navOptions: NavOptions?) {}

  // 3
  override fun createDestination(): Destination {
    return Destination(this)
  }

  // 4
  override fun popBackStack(): Boolean {
    return false
  }

  // 5
  class Destination(authorDetailsNavigator: AuthorDetailsNavigator) :
      NavDestination(authorDetailsNavigator)
}

Going over this step by step:

  1. Here, you declare the name of the tags that you will use for your custom destinations using the @Navigator annotation. The name is what you will add to the nav_graph.xml file the same way as you did for fragment destinations. You also extend the Navigator abstract class.
  2. navigate() is the Navigator function that performs the navigation to your destination.
  3. This is the function that instantiates your type of destination.
  4. This function navigates back through the back stack. You can ignore it in this case and return false .
  5. This nested class holds the data for your custom destination. Its task is to parse all attributes in your destination tag and store them.

Adjust the AuthorDetailsNavigator constructor to take a FragmentManager parameter:

class AuthorDetailsNavigator(
    private val manager: FragmentManager
) : Navigator<AuthorDetailsNavigator.Destination>() {

Add the import statement for the FragmentManager :

import android.support.v4.app.FragmentManager

Next, replace the empty navigate() function with:

override fun navigate(destination: Destination, args: Bundle?, navOptions: NavOptions?) {
  val dialog = AuthorDetailsDialog()
  dialog.arguments = args
  dialog.show(manager, AuthorDetailsDialog.TAG)
}

Import the AuthorDetailsDialog , if required:

import com.raywenderlich.android.bookmanstreasure.ui.authordetails.AuthorDetailsDialog

The function creates a new instance of the AuthorDetailsDialog , sets the Bundle as the dialog arguments and shows it using the FragmentManager .

You now need to tell the Navigation architecture component about your custom destination. Open the MainActivity class and replace

//TODO initialize navigation graph

with the following:

val destination = AuthorDetailsNavigator(navHostFragment.childFragmentManager)
navHostFragment.findNavController().navigatorProvider.addNavigator(destination)

val inflater = navHostFragment.findNavController().navInflater
val graph = inflater.inflate(R.navigation.nav_graph)
navHostFragment.findNavController().graph = graph

You might also need an import statement:

import com.raywenderlich.android.bookmanstreasure.destinations.AuthorDetailsNavigator

In these additions to MainActivity , aside from adding your custom destination, you also inflate the nav_graph.xml file yourself. This ensures the custom author attribute name is recognized.

Define an author details destination in nav_graph.xml :

<author
  android:id="@+id/authorDetails" />

Next, add the following action for the @+id/workDetailsFragment destination:

<action
    android:id="@+id/actionShowAuthor"
    app:destination="@id/authorDetails" />

Open WorkDetailsFragment and replace:

//TODO implement navigation to Author details

With:

findNavController().navigate(
  R.id.actionShowAuthor,
  AuthorDetailsViewModel.createArguments(it)
)

Add the import statement, too:

import com.raywenderlich.android.bookmanstreasure.ui.authordetails.AuthorDetailsViewModel

Finally, open activity_main.xml and remove the app:navGraph="@navigation/nav_graph" attribute on the NavHostFragment fragment tag. If you don't, the app will crash.

Build and run the app. Search for a book, open its details and tap on the author name. If a dialog opens, you're winning!

app-author-details-281x500.png

Common Destinations

To reuse actions, you can declare them outside of any destination tags. Such actions are called global actions .

You'll now make showing author details a global action.

Open nav_graph.xml . Move the @+id/actionShowAuthor action from the @+id/workDetailsFragment destination to instead be a direct child of the navigation tag. Now open BookDetailsFragment and replace

// TODO implement navigation to author details

with the following:

findNavController().navigate(
  R.id.actionShowAuthor,
  AuthorDetailsViewModel.createArguments(it)
)

Add the missing import statements:

import androidx.navigation.fragment.findNavController
import com.raywenderlich.android.bookmanstreasure.ui.authordetails.AuthorDetailsViewModel

Build and run your app. Now, you can open author details from both the Work details and Book edition details screens.

Deep Links

Deep links are URLs that link to a specific screen or content of your app. Your next task is to create a deep link to the Favorites screen.

Open nav_graph.xml file. Look for the fragment tags with the @+id/favoritesFragment ID, and paste this between its tags:

<deepLink app:uri="bookmanstreasure://home/favorites" />

The Navigation architecture component will associate the provided URI with the FavoritesFragment . Open AndroidManifest.xml and add the following between the sole activity tags:

<nav-graph android:value="@navigation/nav_graph" />

Add the code to handle deep links at the end of the onCreate() method in MainActivity :

findNavController(this, R.id.navHostFragment).onHandleDeepLink(intent)

And, finally, add the following implementation of the onNewIntent() method to MainActivity :

override fun onNewIntent(intent: Intent?) {
  super.onNewIntent(intent)
  findNavController(this, R.id.navHostFragment).onHandleDeepLink(intent)
}

Make sure to also add the import statement:

import android.content.Intent

Rebuild the app and open the AndroidManifest.xml file. Click on the Merged Manifest tab at the bottom of the editor. You'll notice that the Navigation architecture component has been added a few items:

generated-manifest-annotated-650x336.png

To test deep links, you'll create a new Run configuration for the app.

Under the Run configurations dropdown, select Edit configurations... option.

edit-run-configurations-650x415.png

Click on the + button in the top-left corner and select Android app from the list. This will create a new empty Run configuration for Android app.

deep-link-test-configuration-annotated-650x459.png

Set the following properties for the new configuration:

  • Under Name , enter Deep link test .
  • Under Module , select app .
  • Under Launch options select URL
  • Under URL , enter bookmanstreasure://home/favorites .

Now, click on Apply and then OK .

Select the new configuration from Run configurations dropdown and run it.

select-deep-link-run-configuration-650x419.png

The app should open the Favorites screen.

app-favourites-281x500.png

Nested graphs

To enhance readability and reusability, one navigation graph can be embedded into another. Create a new navigation graph named book_nav_graph.xml - use the same steps you used as when creating nav_graph.xml .

Replace the auto-generated content for the navigation tag with this:

<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/bookDetailsGraph"
  app:startDestination="@id/workDetailsFragment">

</navigation>

There are two things to note:

workDetailsFragment
bookDetailsGraph

Now, open nav_graph.xml and move the following actions and destinations to book_nav_graph :

actionShowAuthor
workDetailsFragment
bookDetailsFragment
authorDetails

In nav_graph.xml and add the following between the navigation tags:

<include app:graph="@navigation/book_nav_graph" />

Now, open BookSearchFragment and find:

findNavController().navigate(
  R.id.actionBookDetails,
  WorkDetailsViewModel.createArguments(it)
)

Replace it with:

findNavController().navigate(
  R.id.bookDetailsGraph,
  WorkDetailsViewModel.createArguments(it)
)

Note the ID change — you're now pointing to the nested graph. Do the same for FavoritesFragment , then build and run to make sure everything still works!

Conditional Navigation

The last remaining task is to open the Favorites screen if any books have been favorited.

Open nav_graph.xml and add a new destination for LauncherFragment :

<fragment
  android:id="@+id/launcherFragment"
  android:name="com.raywenderlich.android.bookmanstreasure.ui.launcher.LauncherFragment"
  android:label="Blank"
  tools:layout="@layout/fragment_book_details">
  <action
    android:id="@+id/actionBookSearch"
    app:destination="@id/bookSearchFragment" />
  <action
    android:id="@+id/actionFavorites"
    app:destination="@id/favoritesFragment" />
</fragment>

The XML should feel familiar – it's a fragment destination with two actions.

Update the startDestination attribute of the navigation tag to be the following:

app:startDestination="@+id/launcherFragment"

Next, open the LauncherFragment and find:

//TODO implement navigating to Search or Favorites

Replace it with the following:

val destination = if (it.hasFavorites()) {
  R.id.actionFavorites
} else {
  R.id.actionBookSearch
}

findNavController().navigate(
    destination,
    null,
    NavOptions.Builder().setPopUpTo(
        R.id.launcherFragment,
        true
    ).build()
)

Add the following imports:

import androidx.navigation.fragment.findNavController
import androidx.navigation.NavOptions

This will check if there are any favorites and show the appropriate screen. Note the third argument passed to the navigate() function. It specifies navigation options. In this case, it clears the backstack to prevent the user from navigating back to the LauncherFragment .

Note : For a list of all the options available, check the  NavOptions.Builder official documentation .

Now, build an run the app. Search for a book, tap on one of the results and add a favorite by tapping on the ❤︎ icon on the top-right.

app-work-details-favourite-281x500.png

Exit the app, then launch it again. You should land on the Favorites screen.

Where to Go From Here?

You can download the final project using the Download materials button at the top or bottom of the tutorial.

Now you know how to use the Navigation architecture component! Even though it is still in beta, it eases development quite a bit.

A great place to find more information is the official documentation from Google. Also be sure to check outour screencast on the topic.

If you have any questions or tips for others on using the Navigation architecture component, please join in on the forum discussion below!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK