1. Introduction to the Navigation component
1.1 What is the Navigation component
- The Navigation component is an Android Jetpack library that helps developers easily implement navigation in applications. The navigation component contains multiple classes and components, including navigation map, destination, navigation controller, etc., which can help us manage page navigation and task navigation in the application. By using the Navigation component, we can realize the navigation function of the application more conveniently, and at the same time improve the user experience of the application. In this article, we will introduce how to use the Navigation component to implement application navigation, and provide some examples and more extended functions.
1.2 Advantages of the Navigation component
- The Navigation component makes it easy to navigate within an application, including transitions between pages and within an application.
- Navigation components can improve the maintainability and extensibility of an application because they make the structure of the application clearer and make it easier to add new features and pages.
- Navigation components provide a consistent user experience because they use standard navigation patterns and animations.
- Navigation components help developers build applications faster because they provide many common navigation patterns and functionality that can be used directly or modified.
- Navigation components can improve the testability of an application because they make navigation between pages and state transitions more explicit and controllable.
1.3 The Navigation component mainly consists of 3 parts:
- NavHost: The container used to embed the navigation process, generally used
FragmentContainerView
. - NavController:
NavHost
The controller responsible for handling navigation affairs internally, used to perform page jumps, manage return stacks, etc. - NavGraph:
Fragment
A resource file that describes the navigation relationship between pages, where transitions, animations, etc. between pages are defined. Usually placedres/navigation/
in the directory.
In short, Navigation
components are NavHost
used NavGraph
to describe Fragment
navigation paths and relationships, and NavController
then perform actual navigation work, which greatly simplifies the previous page jump logic and rollback stack management process.
2. Basic use of Navigation components
2.1 Add the navigation component to the project
- Add the following dependencies to the project's build.gradle file:
dependencies {
def nav_version = "2.5.3"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
}
- Add NavHostFragment in the layout file:
<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:navGraph="@navigation/nav_graph" />
- Get the NavController object correctly:
When using NavController inside an Activity, it should be onCreate()
fetched in:
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
NavigationUI.setupActionBarWithNavController(this, navController)
And inside the Fragment, it should be fetched in onAttach()
or onViewCreate()
:
val navHostFragment = parentFragment as NavHostFragment
val navController = navHostFragment.navController
NavHost
It is necessary to specify app:navGraph
attributes to associate a navigation graph NavGraph
, which determines Fragment
the navigation relationship and jump path between pages.
NavController
Is Navigation
the component 's control center, used to NavHost
perform navigation operations within. The corresponding instance can be obtained by the attribute passed in Activity
or .Fragment
NavHostFragment
navController
NavController
Common navigation operations are:
- Navigate to the target destination:
navController.navigate(R.id.destination_id)
- Fall back a destination:
navController.navigateUp()
ornavController.popBackStack()
- Fallback to root destination:
navController.popBackStack(R.id.root_destination, false)
NavController
It is also responsible for maintaining Fragment
the rollback stack and popping the stack correctly when the return button is pressed, which greatly simplifies the Fragment
complexity of managing transactions before.
Through the cooperation of NavHost
and NavController
, Navigation
the component implements NavGraph
the navigation logic and page switching function declared in . This makes Fragment
navigating between extremely simple and efficient. Developers only need to focus on the definition NavGraph
, and call NavController
the navigation method in it to realize the page jump, and everything else is under Navigation
the control of the component.
2.2 Create a navigation graph
- Create a navigation graph in an XML file:
<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:navGraph="@navigation/nav_graph" />
- Create a folder under the res folder
navigation
, and then create a file under this foldernav_graph.xml
to define the structure and content of the navigation map:
<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/nav_graph"
app:startDestination="@id/firstFragment">
<fragment
android:id="@+id/firstFragment"
android:name="com.smallmarker.jetpackpractice.navigation.fragment.FirstFragment"
android:label="First"
tools:layout="@layout/fragment_first">
<action
android:id="@+id/action_firstFragment_to_secondFragment"
app:destination="@+id/secondFragment" />
</fragment>
<fragment
android:id="@+id/secondFragment"
android:name="com.smallmarker.jetpackpractice.navigation.fragment.SecondFragment"
android:label="Second"
tools:layout="@layout/fragment_second" />
</navigation>
In a navigation graph, <fragment>
elements are used to define destinations, android:id
attributes are used to specify unique identifiers for destinations, android:name
attributes are used to specify class names for destinations, and android:label
attributes are used to specify label names for destinations as they appear in the application.
<action>
The element is used to define the action, android:id
the attribute is used to specify the unique identifier of the action, and app:destination
the attribute is used to specify the destination for the action to be executed.
3. Advanced use of Navigation components
3.1 Deep linking
In Android, a deep link is a link that takes the user directly to a specific destination within an app. With the Navigation component, you can create two different types of deep links: explicit deep links and implicit deep links.
- Create an explicit deep link:
An explicit deep link is an instance of a deep link that uses a PendingIntent
to take the user to a specific location within the app. For example, you can display explicit deep links in notifications or widget
in .
val pendingIntent = NavDeepLinkBuilder(it)
.setGraph(R.navigation.nav_deep_link)
.setDestination(R.id.deepLinkFragment)
.setArguments(
Bundle().apply {
putInt("id", 1)
}
)
.setComponentName(DeepLinkActivity::class.java)
.createPendingIntent()
val notification = NotificationCompat.Builder(it, "my_channel")
.setContentTitle("Title")
.setContentText("测试深层链接")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pendingIntent)
.build()
NotificationManagerCompat.from(it).notify(Random.nextInt(10), notification)
This example uses NavDeepLinkBuilder
the class construction PendingIntent
, add it to the notification and send it, click on the notification to jump to the specified page.
- Create an implicit deep link:
<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/nav_deep_link"
app:startDestination="@id/deepLinkFragment">
<fragment
android:id="@+id/deepLinkFragment"
android:name="com.smallmarker.jetpackpractice.navigation.fragment.DeepLinkFragment"
android:label="DeepLink"
tools:layout="@layout/fragment_deep_link">
<deepLink app:uri="example://deepLink/{id}" />
</fragment>
</navigation>
To enable implicit deep linking, you must also add content to your app's manifest.xml
file . Add a <nav-graph>
element to point to the existing navigation graph activity
, as shown in the following example.
<activity
android:name=".navigation.DeepLinkActivity"
android:exported="true">
<nav-graph android:value="@navigation/nav_deep_link" />
</activity>
In this example we define a deep link whose URI is "example://deepLink/{id}"
where {itemId}
is a parameter. When the user clicks this link in the browser or other applications, the Android system will automatically open our application, jump to the corresponding page, and pass the parameters to our application. We can get this parameter by passing in the target page arguments
.
3.2 Shared element transitions
- Shared element transitions can realize the animation effect of sharing the same element between different
Activity
orFragment
between. For example, when clicking on a certain item on the list page toitem
enter the details page, the picture or text can beitem
smoothly transitioned between the two pages. Here is an example of a simple implementation:
<!-- 在layout文件中定义共享元素的id -->
<ImageView
android:id="@+id/image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:transitionName="shared_element" />
- Shared element transition to Fragment destination
val extras = FragmentNavigatorExtras(view1 to "shared_element")
view.findNavController().navigate(R.id.confirmationAction, null, null, extras)
- Shared element transition to Activity destination
val option = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, imageView, "shared_element")
findNavController().navigate(R.id.shareElementDialog, null, null, ActivityNavigatorExtras(option))
Shared elements are provided programmatically rather than through navigation XML files. activity
and fragment
Destination each have Navigator.Extras
a subclass of the interface that accepts additional options for navigation, including shared elements. You can pass these navigate()
when Extras
.
3.3 Dynamic construction of navigation graph - Dynamic construction of navigation graph can create different navigation graphs according to different conditions at runtime, such as displaying different navigation structures under different user login statuses and different permissions.
- Here's a simple example of dynamically building a navigation graph:
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
val graph = navInflater.inflate(R.navigation.dynamic_nav_graph)
if (isLoggedIn) {
graph.startDestination = R.id.homeFragment
} else {
graph.startDestination = R.id.loginFragment
}
if (hasAdminPermissions) {
val adminNode = NavGraphNavigator(navController.navigatorProvider.getNavigator(NavGraphNavigator::class.java))
.createDestination()
adminNode.id = R.id.adminFragment
adminNode.setClassName("com.example.app.AdminFragment")
graph.addDestination(adminNode)
graph.addEdge(R.id.homeFragment, R.id.adminFragment)
}
navController.graph = graph
In the above example, we first get the current NavController
sum NavInflater
, and then NavInflater.inflate
load our dynamic navigation map through the method. Next, we set the starting destination of the navigation graph according to different conditions, and dynamically added a destination with administrator privileges, and added an edge to connect this destination and the home page. Finally, we set the built navigation map to NavController
.
4. Best Practices for Navigation Components
4.1 Using <include>
labels
- Define separate for each module
NavGraph
In large projects, it's better to define your own for each functional moduleNavGraph
, then group each module'sNavGraph
with a tag in the root::<include>
NavGraph
<navigation
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/root_navigation">
<include
android:id="@+id/home_navigation"
app:layout="@navigation/home_navigation" />
<include
android:id="@+id/profile_navigation"
app:layout="@navigation/profile_navigation" />
</navigation>
4.1 Using ViewModel and LiveData
- Use LiveData object in ViewModel to handle navigation events:
class MainViewModel : ViewModel() {
private val navigateTo = MutableLiveData<NavDirections>()
fun getNavigateTo(): LiveData<NavDirections> {
return navigateTo
}
fun setNavigateTo(directions: NavDirections) {
navigateTo.value = directions
}
}
- Observe the LiveData object and handle navigation events in Fragment:
class MainFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.getNavigateTo().observe(this) {
Navigation.findNavController(requireView()).navigate(it)
}
}
fun clickJump() {
viewModel.setNavigateTo(MainFragmentDirections.actionMainToNavigationActivity())
}
}
Note that here we use Safe Args
to implement type-safe navigation and navigate between destinations. The official recommendation is to use Safe Args Gradle
plugins. This plugin generates simple object and builder classes for type-safe navigation between destinations. We highly recommend using it when navigating and passing data between destinations Safe Args
.
To Safe Args
add this to your project, build.gradle
include the following in your top-level fileclasspath:
buildscript {
dependencies {
def nav_version = "2.5.3"
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version")
}
}
Then add the following line to your app or module's build.gradle file:
plugins {
id("androidx.navigation.safeargs.kotlin")
}
V. Summary
5.1 Advantages and applicable scenarios of navigation components:
Advantage | Applicable scene |
---|---|
1. Provide a consistent navigation experience | Suitable for scenarios where multiple pages need to be introduced into the application |
2. Simplify the navigation logic | Suitable for scenarios that require complex navigation operations in the application |
3. Customizable Appearance and Behavior | Applicable to scenarios where the navigation bar needs to be customized according to application requirements |
4. Support deep link | Suitable for scenarios that need to support deep linking in the application |
5.2 Best Practices for Navigation Components - When using navigation components, you should minimize manual Fragment transactions, but use the API provided by the navigation components to avoid unnecessary errors.
- When designing a navigation map, try to put pages with similar functions in the same navigation map for easy management and maintenance.
- When using the Safe Args plugin to pass parameters, you should try to use safe types to avoid type conversion errors.
The above is the process of exploring and practicing Android Navigation. For the above example + extension (combined with BottomNavigationView and DrawerLayout), please refer to https://github.com/smallmarker/JetPackPractice