Explaining Android Using Navigation Component and Graphs
Android Jetpack's Navigation Component simplifies handling navigation between different user interface components (such as fragments, activities) in your app by providing a unified framework to express and manage your application’s navigation flow. This component not only centralizes the navigation logic but also generates much of it through a declarative XML file known as a navigation graph. Understanding how to effectively use the Navigation Component and navigation graphs is crucial for efficiently building robust and user-friendly apps.
Navigation Component Overview
1. Components of Navigation Component:
- NavController: A controller object that manages app navigation within a NavHost.
- NavGraph: An XML resource file that describes all the destinations within an app.
- NavHost: A container (such as Activity or Fragment) that hosts destinations within your navigation graph.
- Destination: The screen you can navigate to within your app, such as a Fragment or an Activity.
2. Benefits of Using Navigation Component:
- Simplified Navigation Logic: Centralized handling of navigation reduces complexity in managing user interactions.
- Type Safety: The Navigation Component helps ensure type safety by eliminating string-based identifiers.
- Deep Links: Easier management and support for deep linking URLs directly into app screens.
- Navigation UI Patterns: Helps in implementing standard Android navigation UI patterns like bottom navbars and drawers.
- BackStack Management: Handles back-stack management automatically, streamlining user experience.
- ViewModel Integration: Seamlessly integrates with ViewModel to maintain UI state across configuration changes.
Navigation Graph
1. What is a Navigation Graph?
- A navigation graph, represented as an XML file, defines all destinations and paths that a user can follow within an app.
- It visually represents actions between fragments or activities and manages arguments passed between them.
2. Setting Up a Navigation Graph:
- Open your project in Android Studio.
- Right-click on the
res
directory, select New > Android Resource File. - Name the file (e.g.,
nav_graph.xml
) and choose Navigation from the resource type options.
3. Key Elements of a Navigation Graph:
- Fragment/Activity Destinations: These are the screens you want to navigate to. You add them through the Design view or by editing the XML manually.
- Actions: Actions are defined as directed edges from one destination to another. They represent possible ways to move between screens.
- Arguments: Arguments allow passing data between destinations. This includes types like primitives, strings, and even complex objects like parcelable or serializable classes.
Example:
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_main"
app:startDestination="@id/homeFragment">
<fragment
android:id="@+id/homeFragment"
android:name="com.example.myapp.ui.HomeFragment"
android:label="@string/home">
<!-- Actions -->
<action
android:id="@+id/action_homeFragment_to_secondFragment"
app:destination="@id/secondFragment" />
</fragment>
<fragment
android:id="@+id/secondFragment"
android:name="com.example.myapp.ui.SecondFragment"
android:label="@string/second_fragment">
<!-- Arguments -->
<argument
android:name="userId"
app:argType="integer"/>
</fragment>
</navigation>
Integration With AppCompatActivity or Fragment
1. Adding NavController to an Activity:
- Use the
NavHostFragment
as the root destination within your activity. - Define the
NavHostFragment
in your activity's layout XML file. - Retrieve
NavController
from theNavHostFragment
.
Example in Activity:
// activity_main.xml
<androidx.fragment.app.FragmentContainerView
android:id="@+id/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/nav_main" />
// MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
}
2. Adding NavController to a Fragment:
- Similar to activities, use a child
FragmentContainerView
with a nested nav graph. - Obtain the
NavController
usingfindViewById(R.id.child_nav_host_fragment)
orfindNavController(view)
.
Example in Fragment:
// fragment_home.xml
<androidx.fragment.app.FragmentContainerView
android:id="@+id/child_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:defaultNavHost="false"
app:navGraph="@navigation/child_nav_graph" />
// HomeFragment.java
NavController navController = Navigation.findNavController(view, R.id.child_nav_host_fragment);
Managing BackStack Behavior
1. Default Behavior:
- By default, the Navigation Component correctly manages the back stack based on your navigation graph actions.
2. Customizing BackStack:
- Use attributes like
popUpTo
,popUpToInclusive
, andsaveState
in actions to customize how the back stack behaves.
Example:
<fragment
android:id="@+id/homeFragment"
android:name="com.example.myapp.ui.HomeFragment"
android:label="@string/home">
<action
android:id="@+id/action_homeFragment_to_settingsFragment"
app:destination="@id/settingsFragment"
app:popUpTo="@+id/homeFragment"
app:popUpToInclusive="true" />
- Here, tapping the back button in the
SettingsFragment
will navigate back to theHomeFragment
and remove intermediate destinations.
Implementing Standard Navigation UI Patterns
1. Bottom Navigation:
- Integrate
BottomNavigationView
with the Navigation Component to switch between screens easily. - Link the bottom navigation menu items to the corresponding destinations in the navigation graph.
Example:
// main_activity.xml
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:menu="@menu/navigation_menu" />
// Setting up BottomNavigationView in MainActivity
BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_navigation);
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupWithNavController(bottomNavigationView, navController);
2. Drawer Navigation:
- For navigation drawables, combine
DrawerLayout
,NavigationView
, andToolbar
with the Navigation Component. - Similar to bottom navigation, link the drawer menu items to the respective destinations in the navigation graph.
Example:
// main_activity.xml
<androidx.drawerlayout.widget.DrawerLayout android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout ... >
<androidx.appcompat.widget.Toolbar android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:title="@string/my_title"/>
<fragment
android:id="@+id/nav_host"
....app:defaultNavHost="true"
app:navGraph="@navigation/main_nav_graph" />
</LinearLayout>
<com.google.android.material.navigation.NavigationView android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/nav_header"
app:menu="@menu/navigation_menu" />
</androidx.drawerlayout.widget.DrawerLayout>
// MainActivity.java
DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
NavigationView navigationView = findViewById(R.id.nav_view);
NavController navController = Navigation.findNavController(this, R.id.nav_host);
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout);
NavigationUI.setupWithNavController(navigationView, navController);
Pass data Between Dests
1. Passing Simple Data:
- You can use the
<argument>
element in your navigation graph to pass simple data types (like integers and strings).
Example:
<fragment
android:id="@+id/detailFragment"
android:name="com.example.myapp.ui.DetailFragment">
<argument
android:name="itemId"
app:argType="integer"
android:defaultValue="0" />
- Navigate and pass arguments via:
navController.navigate(R.id.action_detailFragment, bundleOf("itemId" to 123))
2. Passing Complex Data:
- To pass complex data (parcelables or serializables), define
ParcelableArgs
in your navigation graph.
Example:
<fragment
android:id="@+id/profileFragment"
android:name="com.example.myapp.ui.ProfileFragment">
<argument
android:name="profile"
app:argType="com.example.UserProfile"
app:nullable="true" />
- Pass the
UserProfile
object via a bundle:UserProfile userProfile = new UserProfile(); navController.navigate(R.id.action_profileFragment, bundleOf("profile" to userProfile))
Handling Deep Links
1. Adding Deep Links in Navigation Graph:
- Define deep links in your navigation graph to launch specific app screens via URLs.
Example:
<fragment
android:id="@+id/itemDetailFragment"
android:name="com.example.myapp.ui.ItemDetailFragment"
app:deepLinkUri="https://www.example.com/items/{itemId}">
2. Navigating Using Deep Links:
- Capture the deep link intent in your launcher activity and navigate accordingly.
Example in MainActivity:
@Override
public boolean onSupportNavigateUp() {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp();
}
Important Considerations
1. Best Practices:
- Leverage Safe Args to ensure type safety when passing arguments between destinations.
- Avoid hardcoding navigation actions; use resources for IDs.
- Keep navigation graphs modular, especially for large applications to improve readability.
2. Testing:
- Test your navigational flows thoroughly, especially when integrating with ViewModel.
- Use the provided test helpers like
TestNavHost
andNavControllerAssert.assertCurrentDestinationIs()
for unit testing.
3. Performance:
- While the Navigation Component simplifies navigation, be mindful of its performance impact. Large navigation graphs can sometimes slow down your app initialization.
4. Learning Resources:
- Official documentation: Navigation Component
- Online tutorials: Many platforms offer step-by-step guides, including YouTube and various coding blogs.
- Sample apps: Google provides sample applications demonstrating the usage of the Navigation Component.
By incorporating the Navigation Component into your Android projects along with its navigation graphs, you can streamline your development process and create an app with a clean and intuitive user interface. Properly managing navigation, arguments, and deep links enhances the usability and maintainability of your application.
Examples, Set Route and Run the Application Then Data Flow Step-by-Step for Beginners: Android Using Navigation Component and Graphs
The Android Jetpack Navigation component is a comprehensive solution that makes it easy to handle navigation between different parts of an app. This component not only simplifies deep linking but also supports type-safe arguments, handles back button delegation, and supports animations and transitions. If you're just starting out with Android development or are new to using Navigation components, this guide will walk you through setting up routes, running your application, and understanding the data flow.
Step 1: Setup Your Android Project
First, ensure that you have created an Android project in Android Studio. You can do this by selecting “File” -> “New” -> “New Project...” and following the prompts.
Step 2: Add Dependencies
Next, add the necessary dependencies for the Navigation component in your build.gradle
(Module: app) file:
dependencies {
// Navigation components
implementation 'androidx.navigation:navigation-fragment-ktx:2.6.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.6.0'
// ViewModel and LiveData - Optional but recommended
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
// Kotlin Coroutines - Optional but recommended
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
}
Sync your project to download these dependencies.
Step 3: Create Navigation Graph
The Navigation Graph is an XML resource file that centralizes your app’s navigation information. To create one, right-click on the res
directory in your project, go to New
-> Android Resource File
, name it nav_graph.xml
and set its resource type to Navigation
.
Once the graph is created, it will open in the Design tab. Here you can visually manage fragments and their connections.
Adding Fragments:
- Drag and drop the Fragment component from the Palette to the Graph Editor.
Setting Fragment Destination:
- Click on a blank space in the Graph editor, select “Fragment Destination”, and choose the fragment you want to create or use. This adds a new node in the graph representing the destination.
For example, let's add two fragments: HomeFragment
and DetailFragment
.
Step 4: Define Actions Between Fragments
An action represents a directed connection between two destinations in your Navigation graph:
- Go to “Design” view in your
nav_graph.xml
. - Shift-click on
HomeFragment
and then click onDetailFragment
to draw an action line. - A menu will pop up, select “action_homeFragment_to_detailFragment” (or something like this based on the names you chose).
- In the Attributes tab, you can define additional properties like action ID and whether the previous destination should be removed from the back stack.
Step 5: Navigate Between Fragments
In your HomeFragment.kt
, you will find a reference to the NavController
which you will use to navigate.
Add a button in your HomeFragment
layout and attach a click listener in the HomeFragment class. Here’s an example:
Layout of HomeFragment (res/layout/fragment_home.xml):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<Button
android:id="@+id/btn_go_to_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Go to Detail" />
</LinearLayout>
Kotlin code in HomeFragment (HomeFragment.kt):
package com.example.navcomponentexample
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import kotlinx.android.synthetic.main.fragment_home.*
class HomeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_home, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btn_go_to_detail.setOnClickListener {
// Find NavController and navigate
val action = HomeFragmentDirections.actionHomeFragmentToDetailFragment()
findNavController().navigate(action)
}
}
}
In this code, findNavController()
returns a controller that manages app navigation within a host, btn_go_to_detail
is the button reference, and actionHomeFragmentToDetailFragment()
is the generated action object from your navigation graph.
Step 6: Setup NavHost in Activity Layout
The NavHostFragment
acts as a container for your app's navigation graph. It handles all the necessary operations related to navigation.
Modify activity_main.xml to host the navigation graph:
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Here, android:name="androidx.navigation.fragment.NavHostFragment"
is crucial, indicating that this is a host fragment for your Navigation graph. The navGraph
attribute links it to your nav_graph.xml
. defaultNavHost="true"
ensures that navigation handles the system back button appropriately.
Step 7: Initialize NavController in MainActivity
Even though most navigation work in done in fragments, you still need to initialize the NavController in your activity. Here’s how you can do it:
MainActivity.kt:
package com.example.navcomponentexample
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.ui.setupActionBarWithNavController
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navController = findNavController(R.id.nav_host_fragment)
// This couples your NavController with the AppBarConfiguration, handling
// things like the ActionBar title and navigation icon
setupActionBarWithNavController(navController)
}
override fun onSupportNavigateUp(): Boolean {
val navController = this.findNavController(R.id.nav_host_fragment)
return navController.navigateUp() || super.onSupportNavigateUp()
}
}
Step 8: Build and Run Your Application
At this point, you should be able to build and run your application. When you launch, it should display the HomeFragment
by default since that's marked as the start destination in your nav graph. Clicking the button should navigate you to the DetailFragment
.
Step 9: Understanding Data Flow with Safe Args
For passing data between fragments safely and with type information, use Safe Args. First, enable Safe Args plugin in your project-level build.gradle
file:
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'androidx.navigation.safeargs.kotlin' // Add this line
}
Next, add properties to your action in the Navigation graph to pass data:
In nav_graph.xml:
<fragment
android:id="@+id/homeFragment"
android:name="com.example.navcomponentexample.HomeFragment"
android:label="fragment_home">
<action
android:id="@+id/action_homeFragment_to_detailFragment"
app:destination="@id/detailFragment">
<!-- Pass an argument -->
<argument
android:name="username"
app:type="string" />
</action>
</fragment>
<fragment
android:id="@+id/detailFragment"
android:name="com.example.navcomponentexample.DetailFragment"
android:label="fragment_detail">
<argument
android:name="username"
app:type="string"
android:defaultValue="Guest" />
</fragment>
Now, you can modify your HomeFragment
to pass data:
HomeFragment.kt:
// In your click listener
val action = HomeFragmentDirections.actionHomeFragmentToDetailFragment("John Doe")
findNavController().navigate(action)
And retrieve this data in your DetailFragment
:
DetailFragment.kt:
package com.example.navcomponentexample
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.navigation.fragment.navArgs
import kotlinx.android.synthetic.main.fragment_detail.*
class DetailFragment : Fragment() {
private val args: DetailFragmentArgs by navArgs()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_detail, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
tv_username.text = args.username
}
}
Layout for DetailFragment (res/layout/fragment_detail.xml):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:gravity="center">
<TextView
android:id="@+id/tv_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp" />
</LinearLayout>
When you build and run the application now, clicking the button will navigate to DetailFragment
and you should see 'John Doe' displayed on that screen.
Summary
You’ve walked through setting up a simple Navigation graph in an Android project, adding fragments, defining actions between them, setting up a NavHostFragment, passing data securely using Safe Args, and running your application to see the navigation in action. This is a foundational knowledge you can expand upon.
Remember, the key benefit of using the Navigation component is maintaining a clear, declarative structure for navigation. As your app grows and becomes more complex, organizing navigation logic within the Graph will save a lot of hassle and prevent common issues like back button confusion. Happy coding!