Migrate to the Navigation component

The Navigation component is a library that can manage complex navigation, transition animation, deep linking, and compile-time checked argument passing between the screens in your app.

This document serves as a general-purpose guide to migrate an existing app to use the Navigation component.

At a high level, migration involves these steps:

  1. Move screen-specific UI logic out of activities - Move your app’s UI logic out of activities, ensuring that each activity owns only the logic of global navigation UI components, such as a Toolbar, while delegating the implementation of each screen to a fragment or custom destination.

  2. Integrate the Navigation component - For each activity, build a navigation graph which contains the one or more fragments managed by that activity. Replace fragment transactions with Navigation component operations.

  3. Add activity destinations - Replace startActivity() calls with actions using activity destinations.

  4. Combine activities - Combine navigation graphs in cases where multiple activities share a common layout.

Prerequisites

This guide assumes that you have already migrated your app to use AndroidX libraries. If you have not done so, migrate your project to use AndroidX before continuing.

Move screen-specific UI logic out of activities

Activities are system-level components that facilitate a graphical interaction between your app and Android. Activities are registered in your app’s manifest so that Android knows which activities are available to launch. The activity class enables your app to react to Android changes as well, such as when your app’s UI is entering or leaving the foreground, rotating, and so on. The activity can also serve as a place to share state between screens.

Within the context of your app, activities should serve as a host for navigation and should hold the logic and knowledge of how to transition between screens, pass data, and so on. However, managing the details of your UI is better left to a smaller, reusable part of your UI. The recommended implementation for this pattern is fragments. See Single Activity: Why, When, and How to learn more about the advantages of using fragments. Navigation supports fragments via the navigation-fragment dependency. Navigation also supports custom destination types.

If your app is not using fragments, the first thing you need to do is migrate each screen in your app to use a fragment. You aren't removing the activity at this point. Rather, you're creating a fragment to represent the screen and break apart your UI logic by responsibility.

Introducing fragments

To illustrate the process of introducing fragments, let’s start with an example of an application that consists of two screens: a product list screen and a product details screen. Clicking on a product in the list screen takes the user to a details screen to learn more about the product.

In this example, the list and details screens are currently separate activities.

Create a New Layout to Host the UI

To introduce a fragment, start by creating a new layout file for the activity to host the fragment. This replaces the activity’s current content view layout.

For a simple view, you can use a FrameLayout, as shown in the following example product_list_host:

<FrameLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_content"
android:layout_height="match_parent"
android:layout_width="match_parent"/>

The id attribute refers to the content section where we later add the fragment.

Next, in your activity's onCreate() function, modify the layout file reference in your activity’s onCreate function to point to this new layout file:

Kotlin

classProductListActivity:AppCompatActivity(){
...
overridefunonCreate(savedInstanceState:Bundle?){
...
// Replace setContentView(R.layout.product_list) with the line below
setContentView(R.layout.product_list_host)
...
}
}

Java

publicclass ProductListActivityextendsAppCompatActivity{
...
@Override
publicvoidonCreate(@NullableBundlesavedInstanceState){
...
// Replace setContentView(R.layout.product_list); with the line below
setContentView(R.layout.product_list_host);
...
}
}

The existing layout (product_list, in this example) is used as the root view for the fragment you are about to create.

Create a fragment

Create a new fragment to manage the UI for your screen. It's a good practice to be consistent with your activity host name. The snippet below uses ProductListFragment, for example:

Kotlin

classProductListFragment:Fragment(){
// Leave empty for now.
}

Java

publicclass ProductListFragmentextendsFragment{
// Leave empty for now.
}

Move activity logic into a fragment

With the fragment definition in place, the next step is to move the UI logic for this screen from the activity into this new fragment. If you are coming from an activity-based architecture, you likely have a lot of view creation logic happening in your activity's onCreate() function.

Here's an example activity-based screen with UI logic that we need to move:

Kotlin

classProductListActivity:AppCompatActivity(){
// Views and/or ViewDataBinding references, Adapters...
privatelateinitvarproductAdapter:ProductAdapter
privatelateinitvarbinding:ProductListActivityBinding
...
// ViewModels, System Services, other Dependencies...
privatevalviewModel:ProductListViewModelbyviewModels()
...
overridefunonCreate(savedInstanceState:Bundle?){
super.onCreate(savedInstanceState)
// View initialization logic
DataBindingUtil.setContentView(this,R.layout.product_list_activity)
// Post view initialization logic
// Connect adapters
productAdapter=ProductAdapter(productClickCallback)
binding.productsList.setAdapter(productAdapter)
// Initialize view properties, set click listeners, etc.
binding.productsSearchBtn.setOnClickListener{...}
// Subscribe to state
viewModel.products.observe(this,Observer{myProducts->
...
})
// ...and so on
}
...
}

Java

publicclass ProductListActivityextendsAppCompatActivity{
// Views and/or ViewDataBinding references, adapters...
privateProductAdapterproductAdapter;
privateProductListActivityBindingbinding;
...
// ViewModels, system services, other dependencies...
privateProductListViewModelviewModel;
...
@Override
protectedvoidonCreate(@NullableBundlesavedInstanceState){
super.onCreate(savedInstanceState);
// View initialization logic
DataBindingUtil.setContentView(this,R.layout.product_list_activity);
// Post view initialization logic
// Connect adapters
productAdapter=newProductAdapter(productClickCallback);
binding.productsList.setAdapter(productAdapter);
// Initialize ViewModels and other dependencies
ProductListViewModelviewModel=newViewModelProvider(this).get(ProductListViewModel.java);
// Initialize view properties, set click listeners, etc.
binding.productsSearchBtn.setOnClickListener(v->{...});
// Subscribe to state
viewModel.getProducts().observe(this,myProducts->
...
);
// ...and so on
}

Your activity might also be controlling when and how the user navigates to the next screen, as shown in the following example:

Kotlin

// Provided to ProductAdapter in ProductListActivity snippet.
privatevalproductClickCallback=ProductClickCallback{product->
show(product)
}
funshow(product:Product){
valintent=Intent(this,ProductActivity::class.java)
intent.putExtra(ProductActivity.KEY_PRODUCT_ID,product.id)
startActivity(intent)
}

Java

// Provided to ProductAdapter in ProductListActivity snippet.
privateProductClickCallbackproductClickCallback=this::show;
privatevoidshow(Productproduct){
Intentintent=newIntent(this,ProductActivity.class);
intent.putExtra(ProductActivity.KEY_PRODUCT_ID,product.getId());
startActivity(intent);
}

Inside your fragment, you distribute this work between onCreateView() and onViewCreated(), with only the navigation logic remaining in the activity:

Kotlin

classProductListFragment:Fragment(){
privatelateinitvarbinding:ProductListFragmentBinding
privatevalviewModel:ProductListViewModelbyviewModels()
// View initialization logic
overridefunonCreateView(inflater:LayoutInflater,
container:ViewGroup?,
savedInstanceState:Bundle?):View? {
binding=DataBindingUtil.inflate(
inflater,
R.layout.product_list,
container,
false
)
returnbinding.root
}
// Post view initialization logic
overridefunonViewCreated(view:View,savedInstanceState:Bundle?){
// Connect adapters
productAdapter=ProductAdapter(productClickCallback)
binding.productsList.setAdapter(productAdapter)
// Initialize view properties, set click listeners, etc.
binding.productsSearchBtn.setOnClickListener{...}
// Subscribe to state
viewModel.products.observe(this,Observer{myProducts->
...
})
// ...and so on
}
// Provided to ProductAdapter
privatevalproductClickCallback=ProductClickCallback{product->
if(lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)){
(requireActivity()asProductListActivity).show(product)
}
}
...
}

Java

publicclass ProductListFragmentextendsFragment{
privateProductAdapterproductAdapter;
privateProductListFragmentBindingbinding;
// View initialization logic
@Nullable
@Override
publicViewonCreateView(@NonNullLayoutInflaterinflater,
@NullableViewGroupcontainer,
@NullableBundlesavedInstanceState){
binding=DataBindingUtil.inflate(
inflater,
R.layout.product_list_fragment,
container,
false);
returnbinding.getRoot();
}
// Post view initialization logic
@Override
publicvoidonViewCreated(@NonNullViewview,
@NullableBundlesavedInstanceState){
// Connect adapters
binding.productsList.setAdapter(productAdapter);
// Initialize ViewModels and other dependencies
ProductListViewModelviewModel=newViewModelProvider(this)
.get(ProductListViewModel.class);
// Initialize view properties, set click listeners, etc.
binding.productsSearchBtn.setOnClickListener(...)
// Subscribe to state
viewModel.getProducts().observe(this,myProducts->{
...
});
// ...and so on
// Provided to ProductAdapter
privateProductClickCallbackproductClickCallback=newProductClickCallback(){
@Override
publicvoidonClick(Productproduct){
if(getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)){
((ProductListActivity)requireActivity()).show(product);
}
}
};
...
}

In ProductListFragment, notice that there is no call to setContentView() to inflate and connect the layout. In a fragment, onCreateView() initializes the root view. onCreateView() takes an instance of a LayoutInflater which can be used to inflate the root view based on a layout resource file. This example reuses the existing product_list layout which was used by the activity because nothing needs to change to the layout itself.

If you have any UI logic residing in your activity’s onStart(), onResume(), onPause() or onStop() functions that are not related to navigation, you can move those to corresponding functions of the same name on the fragment.

Initialize the fragment in the host activity

Once you have moved all of the UI logic down to the fragment, only navigation logic should remain in the activity.

Kotlin

classProductListActivity:AppCompatActivity(){
overridefunonCreate(savedInstanceState:Bundle?){
super.onCreate(savedInstanceState)
setContentView(R.layout.product_list_host)
}
funshow(product:Product){
valintent=Intent(this,ProductActivity::class.java)
intent.putExtra(ProductActivity.KEY_PRODUCT_ID,product.id)
startActivity(intent)
}
}

Java

publicclass ProductListActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(@NullableBundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.product_list_host);
}
publicvoidshow(Productproduct){
Intentintent=newIntent(this,ProductActivity.class);
intent.putExtra(ProductActivity.KEY_PRODUCT_ID,product.getId());
startActivity(intent);
}
}

The last step is to create an instance of the fragment in onCreate(), just after setting the content view:

Kotlin

overridefunonCreate(savedInstanceState:Bundle?){
super.onCreate(savedInstanceState)
setContentView(R.layout.product_list_host)
if(savedInstanceState==null){
valfragment=ProductListFragment()
supportFragmentManager
.beginTransaction()
.add(R.id.main_content,fragment)
.commit()
}
}

Java

@Override
protectedvoidonCreate(@NullableBundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.product_list_host);
if(savedInstanceState==null){
ProductListFragmentfragment=newProductListFragment();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.main_content,fragment)
.commit();
}
}

As shown in this example, FragmentManager automatically saves and restores fragments over configuration changes, so you only need to add the fragment if the savedInstanceState is null.

Pass intent extras to the fragment

If your activity receives Extras through an intent, you can pass these to the fragment directly as arguments.

In this example, the ProductDetailsFragment receives its arguments directly from the activity’s intent extras:

Kotlin

...
if(savedInstanceState==null){
valfragment=ProductDetailsFragment()
// Intent extras and Fragment Args are both of type android.os.Bundle.
fragment.arguments=intent.extras
supportFragmentManager
.beginTransaction()
.add(R.id.main_content,fragment)
.commit()
}
...

Java

...
if(savedInstanceState==null){
ProductDetailsFragmentfragment=newProductDetailsFragment();
// Intent extras and fragment Args are both of type android.os.Bundle.
fragment.setArguments(getIntent().getExtras());
getSupportFragmentManager()
.beginTransaction()
.add(R.id.main_content,fragment)
.commit();
}
...

At this point, you should be able to test running your app with the first screen updated to use a fragment. Continue to migrate the rest of your activity-based screens, taking time to test after each iteration.

Integrate the Navigation component

Once you're using a fragment-based architecture, you are ready to start integrating the Navigation component.

First, add the most recent Navigation dependencies to your project, following the instructions in the Navigation library release notes.

Create a navigation graph

The Navigation component represents your app’s navigation configuration in a resource file as a graph, much like your app’s views are represented. This helps keep your app’s navigation organized outside of your codebase and provides a way for you to edit your app navigation visually.

To create a navigation graph, start by creating a new resource folder called navigation. To add the graph, right-click on this directory, and choose New > Navigation resource file.

The Navigation component uses an activity as a host for navigation and swaps individual fragments into that host as your users navigate through your app. Before you can start to layout out your app’s navigation visually, you need to configure a NavHost inside of the activity that is going to host this graph. Since we're using fragments, we can use the Navigation component's default NavHost implementation, NavHostFragment.

A NavHostFragment is configured via a FragmentContainerView placed inside of a host activity, as shown in the following example:

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

The app:NavGraph attribute points to the navigation graph associated with this navigation host. Setting this property inflates the nav graph and sets the graph property on the NavHostFragment. The app:defaultNavHost attribute ensures that your NavHostFragment intercepts the system Back button.

If you’re using top-level navigation such as a DrawerLayout or BottomNavigationView, this FragmentContainerView replaces your main content view element. See Update UI components with NavigationUI for examples.

For a simple layout, you can include this FragmentContainerView element as a child of the root ViewGroup:

<FrameLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/main_content"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/product_list_graph"
app:defaultNavHost="true"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>

If you click on the Design tab at the bottom, you should see a graph similar to the one shown below. In the upper left hand side of the graph, under Destinations, you can see a reference to the NavHost activity in the form of layout_name (resource_id).

Click the plus button near the top to add your fragments to this graph.

The Navigation component refers to individual screens as destinations. Destinations can be fragments, activities, or custom destinations. You can add any type of destination to your graph, but note that activity destinations are considered terminal destinations, because once you navigate to an activity destination, you are operating within a separate navigation host and graph.

The Navigation component refers to the way in which users get from one destination to another as actions. Actions can also describe transition animations and pop behavior.

Remove fragment transactions

Now that you are using the Navigation component, if you are navigating between fragment-based screens under the same activity, you can remove FragmentManager interactions.

If your app is using multiple fragments under the same activity or top-level navigation such as a drawer layout or bottom navigation, then you are probably using a FragmentManager and FragmentTransactions to add or replace fragments in the main content section of your UI. This can now be replaced and simplified using the Navigation component by providing actions to link destinations within your graph and then navigating using the NavController.

Here are a few scenarios you might encounter along with how you might approach migration for each scenario.

Single activity managing multiple fragments

If you have a single activity that manages multiple fragments, your activity code might look like this:

Kotlin

classMainActivity:AppCompatActivity(){
overridefunonCreate(savedInstanceState:Bundle?){
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Logic to load the starting destination
// when the Activity is first created
if(savedInstanceState==null){
valfragment=ProductListFragment()
supportFragmentManager.beginTransaction()
.add(R.id.fragment_container,fragment,ProductListFragment.TAG)
.commit()
}
}
// Logic to navigate the user to another destination.
// This may include logic to initialize and set arguments on the destination
// fragment or even transition animations between the fragments (not shown here).
funnavigateToProductDetail(productId:String){
valfragment=newProductDetailsFragment()
valargs=Bundle().apply{
putInt(KEY_PRODUCT_ID,productId)
}
fragment.arguments=args
supportFragmentManager.beginTransaction()
.addToBackStack(ProductDetailsFragment.TAG)
.replace(R.id.fragment_container,fragment,ProductDetailsFragment.TAG)
.commit()
}
}

Java

publicclass MainActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(@NullableBundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Logic to load the starting destination when the activity is first created.
if(savedInstanceState==null){
valfragment=ProductListFragment()
supportFragmentManager.beginTransaction()
.add(R.id.fragment_container,fragment,ProductListFragment.TAG)
.commit();
}
}
// Logic to navigate the user to another destination.
// This may include logic to initialize and set arguments on the destination
// fragment or even transition animations between the fragments (not shown here).
publicvoidnavigateToProductDetail(StringproductId){
Fragmentfragment=newProductDetailsFragment();
Bundleargs=newBundle();
args.putInt(KEY_PRODUCT_ID,productId);
fragment.setArguments(args);
getSupportFragmentManager().beginTransaction()
.addToBackStack(ProductDetailsFragment.TAG)
.replace(R.id.fragment_container,fragment,ProductDetailsFragment.TAG)
.commit();
}
}

Inside of the source destination, you might be invoking a navigation function in response to some event, as shown below:

Kotlin

classProductListFragment:Fragment(){
...
overridefunonViewCreated(view:View,savedInstanceState:Bundle?){
// In this example a callback is passed to respond to an item clicked
// in a RecyclerView
productAdapter=ProductAdapter(productClickCallback)
binding.productsList.setAdapter(productAdapter)
}
...
// The callback makes the call to the activity to make the transition.
privatevalproductClickCallback=ProductClickCallback{product->
(requireActivity()asMainActivity).navigateToProductDetail(product.id)
}
}

Java

publicclass ProductListFragmentextendsFragment{
...
@Override
publicvoidonViewCreated(@NonNullViewview,
@NullableBundlesavedInstanceState){
// In this example a callback is passed to respond to an item clicked in a RecyclerView
productAdapter=newProductAdapter(productClickCallback);
binding.productsList.setAdapter(productAdapter);
}
...
// The callback makes the call to the activity to make the transition.
privateProductClickCallbackproductClickCallback=product->(
((MainActivity)requireActivity()).navigateToProductDetail(product.getId())
);
}

This can be replaced by updating your navigation graph to set the start destination and actions to link your destinations and define arguments where required:

<navigationxmlns: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/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="ProductList"
tools:layout="@layout/product_list">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_detail"/>
</fragment>
<fragment
android:id="@+id/product_detail"
android:name="com.example.android.persistence.ui.ProductDetailFragment"
android:label="ProductDetail"
tools:layout="@layout/product_detail">
<argument
android:name="product_id"
app:argType="integer"/>
</fragment>
</navigation>

Then, you can update your activity:

Kotlin

classMainActivity:AppCompatActivity(){
// No need to load the start destination, handled automatically by the Navigation component
overridefunonCreate(savedInstanceState:Bundle?){
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}

Java

publicclass MainActivityextendsAppCompatActivity{
// No need to load the start destination, handled automatically by the Navigation component
@Override
protectedvoidonCreate(@NullableBundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}

The activity no longer needs a navigateToProductDetail() method. In the next section, we update ProductListFragment to use the NavController to navigate to the next product detail screen.

Pass arguments safely

The Navigation component has a Gradle plugin called Safe Args that generates simple object and builder classes for type-safe access to arguments specified for destinations and actions.

Once the plugin is applied, any arguments defined on a destination in your navigation graph causes the Navigation component framework to generate an Arguments class that provides type safe arguments to the target destination. Defining an action causes the plugin to generate a Directions configuration class which can be used to tell the NavController how to navigate the user to the target destination. When an action points to a destination that requires arguments, the generated Directions class includes constructor methods which require those parameters.

Inside the fragment, use NavController and the generated Directions class to provide type-safe arguments to the target destination, as shown in the following example:

Kotlin

classProductListFragment:Fragment(){
overridefunonViewCreated(view:View,savedInstanceState:Bundle?){
// In this example a callback is passed to respond to an item clicked in a RecyclerView
productAdapter=ProductAdapter(productClickCallback)
binding.productsList.setAdapter(productAdapter)
}
...
// The callback makes the call to the NavController to make the transition.
privatevalproductClickCallback=ProductClickCallback{product->
valdirections=ProductListDirections.navigateToProductDetail(product.id)
findNavController().navigate(directions)
}
}

Java

publicclass ProductListFragmentextendsFragment{
...
@Override
publicvoidonViewCreated(@NonNullViewview,
@NullableBundlesavedInstanceState){
// In this example a callback is passed to respond to an item clicked in a RecyclerView
productAdapter=newProductAdapter(productClickCallback);
binding.productsList.setAdapter(productAdapter);
}
...
// The callback makes the call to the activity to make the transition.
privateProductClickCallbackproductClickCallback=product->{
ProductListDirections.ViewProductDetailsdirections=
ProductListDirections.navigateToProductDetail(product.getId());
NavHostFragment.findNavController(this).navigate(directions);
};
}

Top-Level Navigation

If your app uses a DrawerLayout, you might have a lot of configuration logic in your activity that manages opening and closing the drawer and navigating to other destinations.

Your resulting activity might look something like this:

Kotlin

classMainActivity:AppCompatActivity(),
NavigationView.OnNavigationItemSelectedListener{
overridefunonCreate(savedInstanceState:Bundle?){
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
valtoolbar:Toolbar=findViewById(R.id.toolbar)
setSupportActionBar(toolbar)
valdrawerLayout:DrawerLayout=findViewById(R.id.drawer_layout)
valnavView:NavigationView=findViewById(R.id.nav_view)
valtoggle=ActionBarDrawerToggle(
this,
drawerLayout,
toolbar,
R.string.navigation_drawer_open,
R.string.navigation_drawer_close
)
drawerLayout.addDrawerListener(toggle)
toggle.syncState()
navView.setNavigationItemSelectedListener(this)
}
overridefunonBackPressed(){
valdrawerLayout:DrawerLayout=findViewById(R.id.drawer_layout)
if(drawerLayout.isDrawerOpen(GravityCompat.START)){
drawerLayout.closeDrawer(GravityCompat.START)
}else{
super.onBackPressed()
}
}
overridefunonNavigationItemSelected(item:MenuItem):Boolean{
// Handle navigation view item clicks here.
when(item.itemId){
R.id.home->{
valhomeFragment=HomeFragment()
show(homeFragment)
}
R.id.gallery->{
valgalleryFragment=GalleryFragment()
show(galleryFragment)
}
R.id.slide_show->{
valslideShowFragment=SlideShowFragment()
show(slideShowFragment)
}
R.id.tools->{
valtoolsFragment=ToolsFragment()
show(toolsFragment)
}
}
valdrawerLayout:DrawerLayout=findViewById(R.id.drawer_layout)
drawerLayout.closeDrawer(GravityCompat.START)
returntrue
}
}
privatefunshow(fragment:Fragment){
valdrawerLayout=drawer_layoutasDrawerLayout
valfragmentManager=supportFragmentManager
fragmentManager
.beginTransaction()
.replace(R.id.main_content,fragment)
.commit()
drawerLayout.closeDrawer(GravityCompat.START)
}

Java

publicclass MainActivityextendsAppCompatActivity
implementsNavigationView.OnNavigationItemSelectedListener{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbartoolbar=findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
DrawerLayoutdrawer=findViewById(R.id.drawer_layout);
NavigationViewnavigationView=findViewById(R.id.nav_view);
ActionBarDrawerToggletoggle=newActionBarDrawerToggle(
this,
drawer,
toolbar,
R.string.navigation_drawer_open,
R.string.navigation_drawer_close);
drawer.addDrawerListener(toggle);
toggle.syncState();
navigationView.setNavigationItemSelectedListener(this);
}
@Override
publicvoidonBackPressed(){
DrawerLayoutdrawer=findViewById(R.id.drawer_layout);
if(drawer.isDrawerOpen(GravityCompat.START)){
drawer.closeDrawer(GravityCompat.START);
}else{
super.onBackPressed();
}
}
@Override
publicbooleanonNavigationItemSelected(MenuItemitem){
// Handle navigation view item clicks here.
intid=item.getItemId();
if(id==R.id.home){
FragmenthomeFragment=newHomeFragment();
show(homeFragment);
}elseif(id==R.id.gallery){
FragmentgalleryFragment=newGalleryFragment();
show(galleryFragment);
}elseif(id==R.id.slide_show){
FragmentslideShowFragment=newSlideShowFragment();
show(slideShowFragment);
}elseif(id==R.id.tools){
FragmenttoolsFragment=newToolsFragment();
show(toolsFragment);
}
DrawerLayoutdrawer=findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
returntrue;
}
privatevoidshow(Fragmentfragment){
DrawerLayoutdrawerLayout=findViewById(R.id.drawer_layout);
FragmentManagerfragmentManager=getSupportFragmentManager();
fragmentManager
.beginTransaction()
.replace(R.id.main_content,fragment)
.commit();
drawerLayout.closeDrawer(GravityCompat.START);
}
}

After you have added the Navigation component to your project and created a navigation graph, add each of the content destinations from your graph (such as Home, Gallery, SlideShow, and Tools from the example above). Be sure that your menu item id values match their associated destination id values, as shown below:

<!--activity_main_drawer.xml-->
<menuxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
<groupandroid:checkableBehavior="single">
<item
android:id="@+id/home"
android:icon="@drawable/ic_menu_camera"
android:title="@string/menu_home"/>
<item
android:id="@+id/gallery"
android:icon="@drawable/ic_menu_gallery"
android:title="@string/menu_gallery"/>
<item
android:id="@+id/slide_show"
android:icon="@drawable/ic_menu_slideshow"
android:title="@string/menu_slideshow"/>
<item
android:id="@+id/tools"
android:icon="@drawable/ic_menu_manage"
android:title="@string/menu_tools"/>
</group>
</menu>
<!--activity_main_graph.xml-->
<navigationxmlns: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/main_graph"
app:startDestination="@id/home">
<fragment
android:id="@+id/home"
android:name="com.example.HomeFragment"
android:label="Home"
tools:layout="@layout/home"/>
<fragment
android:id="@+id/gallery"
android:name="com.example.GalleryFragment"
android:label="Gallery"
tools:layout="@layout/gallery"/>
<fragment
android:id="@+id/slide_show"
android:name="com.example.SlideShowFragment"
android:label="SlideShow"
tools:layout="@layout/slide_show"/>
<fragment
android:id="@+id/tools"
android:name="com.example.ToolsFragment"
android:label="Tools"
tools:layout="@layout/tools"/>
</navigation>

If you match the id values from your menu and graph, then you can wire up the NavController for this activity to handle navigation automatically based on the menu item. The NavController also handles opening and closing the DrawerLayout and handling Up and Back button behavior appropriately.

Your MainActivity can then be updated to wire up the NavController to the Toolbar and NavigationView.

See the following snippet for an example:

Kotlin

classMainActivity:AppCompatActivity(){
valdrawerLayoutbylazy{findViewById<DrawerLayout>(R.id.drawer_layout)}
valnavControllerbylazy{
(supportFragmentManager.findFragmentById(R.id.main_content)asNavHostFragment).navController
}
valnavigationViewbylazy{findViewById<NavigationView>(R.id.nav_view)}
overridefunonCreate(savedInstanceState:Bundle?){
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
valtoolbar=findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
// Show and Manage the Drawer and Back Icon
setupActionBarWithNavController(navController,drawerLayout)
// Handle Navigation item clicks
// This works with no further action on your part if the menu and destination id’s match.
navigationView.setupWithNavController(navController)
}
overridefunonSupportNavigateUp():Boolean{
// Allows NavigationUI to support proper up navigation or the drawer layout
// drawer menu, depending on the situation
returnnavController.navigateUp(drawerLayout)
}
}

Java

publicclass MainActivityextendsAppCompatActivity{
privateDrawerLayoutdrawerLayout;
privateNavControllernavController;
privateNavigationViewnavigationView;
@Override
protectedvoidonCreate(@NullableBundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
drawerLayout=findViewById(R.id.drawer_layout);
NavHostFragmentnavHostFragment=(NavHostFragment)
getSupportFragmentManager().findFragmentById(R.id.main_content);
navController=navHostFragment.getNavController();
navigationView=findViewById(R.id.nav_view);
Toolbartoolbar=findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// Show and Manage the Drawer and Back Icon
NavigationUI.setupActionBarWithNavController(this,navController,drawerLayout);
// Handle Navigation item clicks
// This works with no further action on your part if the menu and destination id’s match.
NavigationUI.setupWithNavController(navigationView,navController);
}
@Override
publicbooleanonSupportNavigateUp(){
// Allows NavigationUI to support proper up navigation or the drawer layout
// drawer menu, depending on the situation.
returnNavigationUI.navigateUp(navController,drawerLayout);
}
}

You can use this same technique with both BottomNavigationView-based navigation and Menu-based navigation. See Update UI components with NavigationUI for more examples.

Add activity destinations

Once each screen in your app is wired up to use the Navigation component, and you are no longer using FragmentTransactions to transition between fragment-based destinations, the next step is to eliminate startActivity calls.

First, identify places in your app where you have two separate navigation graphs and are using startActivity to transition between them.

This example contains two graphs (A and B) and a startActivity() call to transition from A to B.

Kotlin

funnavigateToProductDetails(productId:String){
valintent=Intent(this,ProductDetailsActivity::class.java)
intent.putExtra(KEY_PRODUCT_ID,productId)
startActivity(intent)
}

Java

privatevoidnavigateToProductDetails(StringproductId){
Intentintent=newIntent(this,ProductDetailsActivity.class);
intent.putExtra(KEY_PRODUCT_ID,productId);
startActivity(intent);

Next, replace these with an activity destination in Graph A that represents the navigation to the host activity of Graph B. If you have arguments to pass to the start destination of Graph B, you can designate them in the activity destination definition.

In the following example, Graph A defines an activity destination which takes a product_id argument along with an action. Graph B contains no changes.

The XML representation of Graphs A and B might look like this:

<!--GraphA-->
<navigationxmlns: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/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="ProductList"
tools:layout="@layout/product_list_fragment">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_details_activity"/>
</fragment>
<activity
android:id="@+id/product_details_activity"
android:name="com.example.android.persistence.ui.ProductDetailsActivity"
android:label="ProductDetails"
tools:layout="@layout/product_details_host">
<argument
android:name="product_id"
app:argType="integer"/>
</activity>
</navigation>
<!--GraphB-->
<navigationxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
app:startDestination="@id/product_details">
<fragment
android:id="@+id/product_details"
android:name="com.example.android.persistence.ui.ProductDetailsFragment"
android:label="ProductDetails"
tools:layout="@layout/product_details_fragment">
<argument
android:name="product_id"
app:argType="integer"/>
</fragment>
</navigation>

You can navigate to the host activity of Graph B using the same mechanisms you use to navigate to fragment destinations:

Kotlin

funnavigateToProductDetails(productId:String){
valdirections=ProductListDirections.navigateToProductDetail(productId)
findNavController().navigate(directions)
}

Java

privatevoidnavigateToProductDetails(StringproductId){
ProductListDirections.NavigateToProductDetaildirections=
ProductListDirections.navigateToProductDetail(productId);
Navigation.findNavController(getView()).navigate(directions);

Pass activity destination args to a start destination fragment

If the destination activity receives extras, as with the previous example, you can pass these to the start destination directly as arguments, but you need to manually set your host’s navigation graph inside the host activity’s onCreate() method so that you can pass the intent extras as arguments to the fragment, as shown below:

Kotlin

classProductDetailsActivity:AppCompatActivity(){
overridefunonCreate(savedInstanceState:Bundle?){
super.onCreate(savedInstanceState)
setContentView(R.layout.product_details_host)
valnavHostFragment=supportFragmentManager.findFragmentById(R.id.main_content)asNavHostFragment
valnavController=navHostFragment.navController
navController
.setGraph(R.navigation.product_detail_graph,intent.extras)
}
}

Java

publicclass ProductDetailsActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(@NullableBundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.product_details_host);
NavHostFragmentnavHostFragment=(NavHostFragment)
getSupportFragmentManager().findFragmentById(R.id.main_content);
NavControllernavController=navHostFragment.getNavController();
navController
.setGraph(R.navigation.product_detail_graph,getIntent().getExtras());
}
}

The data can be pulled out of the fragment arguments Bundle using the generated args class, as shown in the following example:

Kotlin

classProductDetailsFragment:Fragment(){
valargsbynavArgs<ProductDetailsArgs>()
overridefunonViewCreated(view:View,savedInstanceState:Bundle?){
valproductId=args.productId
...
}
...

Java

publicclass ProductDetailsFragmentextendsFragment{
ProductDetailsArgsargs;
@Override
publicvoidonCreate(@NullableBundlesavedInstanceState){
super.onCreate(savedInstanceState);
args=ProductDetailsArgs.fromBundle(requireArguments());
}
@Override
publicvoidonViewCreated(@NonNullViewview,
@NullableBundlesavedInstanceState){
intproductId=args.getProductId();
...
}
...

Combine activities

You can combine navigation graphs in cases where multiple activities share the same layout, such as a simple FrameLayout containing a single fragment. In most of these cases, you can just combine all of the elements from each navigation graph and updating any activity destination elements to fragment destinations.

The following example combines Graphs A and B from the previous section:

Before combining:

<!--GraphA-->
<navigationxmlns: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/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="ProductListFragment"
tools:layout="@layout/product_list">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_details_activity"/>
</fragment>
<activity
android:id="@+id/product_details_activity"
android:name="com.example.android.persistence.ui.ProductDetailsActivity"
android:label="ProductDetailsHost"
tools:layout="@layout/product_details_host">
<argumentandroid:name="product_id"
app:argType="integer"/>
</activity>
</navigation>
<!--GraphB-->
<navigationxmlns: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/product_detail_graph"
app:startDestination="@id/product_details">
<fragment
android:id="@+id/product_details"
android:name="com.example.android.persistence.ui.ProductDetailsFragment"
android:label="ProductDetails"
tools:layout="@layout/product_details">
<argument
android:name="product_id"
app:argType="integer"/>
</fragment>
</navigation>

After combining:

<!--CombinedGraphAandB-->
<navigationxmlns: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/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="ProductListFragment"
tools:layout="@layout/product_list">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_details"/>
</fragment>
<fragment
android:id="@+id/product_details"
android:name="com.example.android.persistence.ui.ProductDetailsFragment"
android:label="ProductDetails"
tools:layout="@layout/product_details">
<argument
android:name="product_id"
app:argType="integer"/>
</fragment>
</navigation>

Keeping your action names the same while merging can make this a seamless process, requiring no changes to your existing code base. For example, navigateToProductDetail remains the same here. The only difference is that this action now represents navigation to a fragment destination within the same NavHost instead of an activity destination:

Kotlin

funnavigateToProductDetails(productId:String){
valdirections=ProductListDirections.navigateToProductDetail(productId)
findNavController().navigate(directions)
}

Java

privatevoidnavigateToProductDetails(StringproductId){
ProductListDirections.NavigateToProductDetaildirections=
ProductListDirections.navigateToProductDetail(productId);
Navigation.findNavController(getView()).navigate(directions);

Additional Resources

For more navigation-related information, see the following topics:

Content and code samples on this page are subject to the licenses described in the Content License. Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.

Last updated 2025年02月10日 UTC.