App Navigation in Android
Principles of navigation
- Fixed Start Destination
- Every application you build has a fixed start destination.
- This is the first screen the user sees whey they launch your application from the launcher. This destination is also the last screen the user sees whey they return to the launcher after pressing the Back button.
- Navigation state is represented as a stack of destinations.
- When your application is first launched, a new task is created for the user and application displays its start destination.
- The top of the stack is the current screen.
- The back stack always has the start destination of the application at the bottom of the stack.
- Operations that change the back stack always operate on the top of the stack, either by pushing a new destination onto the top of the stack or popping the top-most destination off the stack.
- Navigating to a destination pushes that destination on top of the stack.
- Up and Back are identical within your application's task
- When you press the Back button, the current destination is popped off the top of the back stack, and you then navigate to the previous destination.
- Up button appears at the left in the app bar at the top of the screen.
- Within your application's task, the Up and Back button's behave identically.
- The Up button never exits your application
- If a user is at the application's start destination, then the Up button does not appear, because the Up button never exits the application.
- The Back button is shown and does exit the application.
- Deep linking simulates manual navigation
- The synthetic back stack created by deep link should match a back stack that could have been achieved by organically navigating through the application.
- The Navigation component supports deep linking and recreates a realistic back stack for you when linking to any destination in your navigation graph.
Design for different form factors
The user interface of your application is drawn inside of a window, the size of which can change at will.
- Designing responsive content.
- It's often necessary to provide more content on larger devices to avoid leaving too much whitespace on your screen.
- To solve this problem refer Providing Alternate Resources link.
- Providing tailored user experiences
- Follow material.io
Handling configuration changes
- Responsive UI and navigation
- To provide the best possible navigation experience to your users, you should provide a navigation UI that is tailored to the width, height, and smallest-width of the user's device.
- When one or more environmental properties change at runtime, the Android OS responds by destroying and then recreating the activities and fragments of your application.
- Make sure to use resource configuration qualifiers where appropriate and avoid the use of hard-coded layout sizes.
- Implementing global navigation in a responsive UI
- Android follows an order of precedence when determining which resources to apply.
- If the device configuration changes, Android destroys the activity from the previous configuration along with its associated views. It then recreates the activity with resources designed for the new configuration. The activity, being destroyed and recreated, then automatically wires up the proper global navigation elements in onCreate()
- While the navigation UI might change, the navigation graph should not change between configurations. NavHostFragment saves and restores the current graph state between configuration changes. During the restoration process, all restored destinations are assumed to exist on the current graph.
- Consider alternatives to split-view layouts
- While it's possible to implement a split-view layout interface using the Navigation library, you should consider other alternatives.
- Destination names
- If you provide destination names in your graph using the android:label attribute, be sure to always use resource values so that your content can still be localized.
Navigation
- Navigation refers to the interactions that allow the users to navigate across, into and back out from the different pieces of content within your application.
- The navigation component consists of three key parts:
- Navigation graph
- An XML resource that contains all navigation related information in one centralized location.
- NavHost
- An empty container that displays destinations from your navigation graph.
- NavController
- An object that manages app navigation within a NavHost.
- Navigation component also provides
- Handling fragment transactions
- Handling Up and Back actions correctly by default.
- Providing standardized resources for animations and transitions.
- Implementing and handling deep linking.
- Including Navigation UI patterns, such as navigation drawers and bottom navigation, with minimal additional work.
- Safe Args - provides type safety when navigating and passing data between destinations.
- ViewModel support
Get started with the Navigation component
- Set up your environment
- Add the following dependencies in build.gradle file (Module).
def nav_version = "2.4.1"
// Java language implementation
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
// Kotlin
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
// Feature module Support
implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"
// Testing Navigation
androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
// Jetpack Compose Integration
implementation "androidx.navigation:navigation-compose:$nav_version"
- Create a navigation graph
- A navigation graph is a resource file that contains all of your destinations and actions. The graph represents all of your application's navigation paths.
- 1 represents destinations. Destinations are the different content areas in your application.
- 2 represents Actions. Actions are logical connections between your destinations that represents paths that users can take.
- To add a navigation graph to your project do the following.
- Select res directory and New > Android Resource File
- Give a name to the files as nav_graph.
- Select Navigation from the Resource Type drop-down list.
- The <navigation> element is the root element of a navigation graph.
- If you have nested graphs, they appear as child <navigation> elements.
- Navigation Editor
- 1 represents Destinations panel. Destinations panel lists your navigation host and all destinations currently in the Graph Editor.
- 2 represents Graph Editor. Graph Editor contains a visual representation of your navigation graph. You can switch between the Design view and the underlying XML representation in the Text view.
- 3 represents Attributes. Attributes shows attributes for the currently-selected item in the navigation graph.
- Add a NavHost to an activity
- The navigation host is an empty container where destinations are swapped in and out as a user navigates through your application.
- The Navigation component is designed for applications that have one main activity with multiple fragment destinations. The main activity is associated with a navigation graph and contains a NavHostFragment that is responsible for swapping destinations as needed. In an application with multiple activity destinations, each activity has its own navigation graph.
- Add a NavHostFragment via XML
- android:name attribute contains the class name of your NavHost implementation.
- app:navGraph attribute associates the NavHostFragment with a navigation graph. The navigation graph specifies all of the destinations in this NavHostFragment to which users can navigate.
- app:defaultNavHost = "true" attribute ensures that your NavHostFragment intercepts the system Back button. Note that only one NavHost can be the default. If you have multiple hosts in the same layout (two-pane layouts) be sure to specify only one default NavHost.
- Add destinations to the navigation graph
- You can create a destination from an existing fragment or activity.
- To know more follow the link of the header.
- Anatomy of destination
- Type
- Indicates whether the destination is implemented as fragment, activity or other custom class in your source code.
- Label
- Contains the user-readable name of the destination. It is recommended that you use resource strings for this value.
- ID
- Contains the ID of the destination which is used to refer to the destination in code.
- Class
- Shows the name of the class that is associated with the destination. You can click this dropdown to change the associated class to another destination type.
- Designate a screen as the start destination
- The start destination is the first screen users see when opening your application and it's the last screen users see when exiting your application.
- To know more follow the link of the header.
- Connect destinations
- An action is a logical connection between destinations. Actions are represented in the navigation graph as arrows. Actions usually connect one destination to another, though you can also create global actions that take you to a specific destination from anywhere in your application.
- To know more follow the link of the header.
- Navigate to a destination
- Navigating to a destination is done using a NavController, an object that manages app navigation within a NavHost.
- Each NavHost has its own corresponding NavController.
- Use one of the methods to retrieve a NavController:
- When you use FragmentContainerView to create NavHostFragment or manually add the NavHostFragment to your activity via a FragmentTransaction, then attempting to retrieve the NavController in onCreate() of an Activity via Navigation.findNavController(Activity, @IdRes int) will fail.
- You should retrieve the NavController directly from the NavHostFragment.
- Ensure type-safety by using Safe-Args
- The recommended way to navigate between destinations is to use the Safe Args Gradle plugin.
- Add the plugin in build.gradle (Project)
buildscript {
repositories {
google()
}
dependencies {
def nav_version = "2.4.1"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
}
plugins {
id 'com.android.application' version '7.1.2' apply false
id 'com.android.library' version '7.1.2' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}
- Add the plugin in build.gradle (Module) for Kotlin
plugins {
id 'androidx.navigation.safeargs.kotlin'
}
- You must have android.useAndroidX=true in your gragle.properties file as per Migrating to AndroidX
- After you enable Safe Args, the plugin generates code that contains classes and methods for each action you've defined.
- The generated class name is a combination of the originating destination class and the word "Directions" e.g. SpecifyAmountFragmentDirections. Here, SpecifyAmountFragment is the name of the destination.
- The generated class contains a static method for each action defined in the originating destination. This method takes any defined action parameters as arguments and returns a NavDirections object that you can pass directly to navigate().
override fun onClick(view: View) {
val action =
SpecifyAmountFragmentDirections
.actionSpecifyAmountFragmentToConfirmationFragment()
view.findNavController().navigate(action)
}
Create Destinations
You can create a destination from an existing fragment or activity.
- Create a destination from an existing fragment or activity
- To know more follow the link of the header.
- Create a new fragment destination
- To know more follow the link of the header.
- Create a destination from DialogFragment
- If you have an existing DialogFragment, you can use the <dialog> element to add the dialog to your navigation graph.
- Create a new activity destination
- Creating an Activity destination is similar to creating a Fragment destination. However, the nature of Activity destination is quite different.
- If a user navigates to a different Activity, the current navigation graph is no longer in scope.
- An Activity destination is considered to be an endpoint within a navigation graph.
- In case if an Activity has an intent-filter in the manifest file, then the corresponding Activity destination needs to be configured with action and data attributes matching those in the manifest entry.
- Specifying targetPackage to the current applicationId limits the scope to the current application, which includes the main application.
- Dynamic arguments
- Use app:dataPattern to supply dynamic arguments.
- To know more follow the link of the header.
- Placeholder destinations
- You can use placeholders to represent unimplemented destinations. A placeholder serves as a visual representation of a destination.
Design navigation graphs
The Navigation component uses a navigation graph to manage your application's navigation. You can manage your application's navigation graph using the Navigation Editor in Android Studio.
- Top-level navigation graph
- Your application's top-level navigation graph should start with the initial destination the user sees when the application is launched and should include the destinations that they see as the user move about your application.
- Nested graphs
- Sub-flows within your application can be represented as nested navigation graphs.
- Nested graphs are reusable. They also provide a level of encapsulation - destinations outside of the nested graph do not have direct access to any of the destinations within the nested graph.
- Another way to modularize your graph structure is to include one graph within another via an <include> element in the parent navigation graph.
- Navigation across library modules
- If your application depends on library modules, which have a navigation graph contained therein, you can reference these navigation graphs using an <include> element.
- Global actions
- Any destination in your application that can be reached through more than one path should have a corresponding global action defined to navigate to that destination.
- Global actions can be used to navigate to a destination from anywhere.
- To view the XML file click the header.
Nested navigation graphs
A series of destinations can be grouped into a nested graph within a parent navigation graph called the root graph. Nested graphs are useful to organize and reuse sections of your application's UI.
A nested graph must have a destination identified as the start destination.
To group destinations into a nested graph follow the link of the header.
- Reference other navigation graphs with <include>
- Within a navigation graph, you can reference other graphs by using <include>
Global actions
We can use a global action to create a common action that multiple destinations can use.
- Create a global action
- To know more follow the link of the header.
- Use a global action
- To use a global action in your code, pass the resource ID of the global action to the navigate() method for reach UI element.
viewTransactionButton.setOnClickListener { view ->
view.findNavController().navigate(R.id.action_global_mainFragment)
}
Navigate to a destination
Navigating to a destination is done using NavController, an object that manages application navigation within a NavHost. Each NavHost has its own corresponding NavController.
To retrieve NavController for a fragment, activity or view we can use the following methods.
- Kotlin
- Java
- NavHostFragment.findNavController(Fragment)
- Navigation.findNavController(Activity, @IdRes int viewId)
- Navigation.findNavController(View)
- Use Safe Args to navigate with type-safety
- The recommended way to navigate between destinations is to use the Safe Args Gradle plugin.
- To add Safe Args into your project include the following classpath in your top level build.gradle (Project) file.
buildscript {
repositories {
google()
}
dependencies {
def nav_version = "2.4.1"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
}
plugins {
id 'com.android.application' version '7.1.2' apply false
id 'com.android.library' version '7.1.2' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}
- Also add the required plugin in build.gradle (Module) file for Kotlin.
plugins {
id 'androidx.navigation.safeargs.kotlin'
}
- You must have android.useAndroidX=true in your gragle.properties file as per Migrating to AndroidX
- After you enable Safe Args, the plugin generates code that contains classes and methods for each action you've defined.
- The generated class name is a combination of the originating destination class and the word "Directions" e.g. SpecifyAmountFragmentDirections. Here, SpecifyAmountFragment is the name of the destination.
- The generated class contains a static method for each action defined in the originating destination. This method takes any defined action parameters as arguments and returns a NavDirections object that you can pass directly to navigate().
- Navigate using ID
- navigate(int) takes the resource ID of either an action or a destination.
- Note: When navigating using IDs, we strongly recommend using actions where possible. Actions provide additional information in your navigation graph, visually showing how your destinations connect to each other. By creating actions, you can replace resource IDs with Safe Args-generated operations, providing additional compile-time safety. By using an action, you can also animate transitions between the destinations. For more information, see Animate transitions between destinations.
- Provide navigation options to actions
- When you define an action in the navigation graph, Navigation generates a corresponding NavAction class, which contains the configurations defined for that action, including the following
- Destination
- The resource ID of the target destination.
- Default Arguments
- Contains default values for the target destination.
- Navigation options
- Navigation options are represented as NavOptions. This class contains all of the special configuration for transitioning to and back from the target destination, including animation resource configuration, pop behavior, and whether the destination should be launched in single top mode.
- Apply NavOptions programmatically.
- NavOptions that are applied programmatically will override any and all options that have been sent in XML.
- You can also programmatically apply NavOptions when navigating to implicit deep links.
- To learn the implementation follow the link of the header.
- Navigate using DeepLinkRequest
- You can use the method navigate(NavDeepLinkRequest) to navigate directly to an implicit deep link destination.
- To add an action to the request, use fromAction() or setAction() method.
- To add a MIME type to a request, use fromMimeType() or setMimeType() method.
- For a NavDeepLinkRequest to properly match an implicit deep link destination, the URI, action, and MIME type must all match the NavDeepLink in the destination.
- URIs must match the pattern, the actions must be an exact match, and the MIME types must be related.
- You can navigate to a destination on the current graph or a destination on a completely different graph.
- For convenience, you can use navigate(Uri), which wraps a Uri in a DeepLinkRequest.
- Navigation and the back stack
Fragments
A Fragment represents a reusable portion of your app's UI. A fragment defines and manages its own layout, has its own lifecycle, and can handle its own input events. Fragments cannot live on their own: they must be hosted by an activity or another fragment. The fragment's view hierarchy becomes a part of, or attaches to, the host's view hierarchy.
- Modularity
- Activities are an ideal place to put global elements around your app's user interface, such an a navigation drawer. Conversely, fragments are better suited to define and manage the UI of a single screen or portion of a screen.
- While your activity is in the STARTED lifecycle state or higher, fragments can be added, replaced, or removed.
- You can keep a record of these changes in a back stack that is managed by the activity, allowing the changes to be reversed.
- You can use multiple instances of the same fragment class within the same activity, in multiple activities, or even as a child of another fragment.
- You should only provide a fragment with the logic necessary to manage its own UI.
- You should avoid depending on or manipulating one fragment from another.
Create a fragment
A fragment has its own lifecycle, receives its own input events, and you can add or remove fragments while the containing activity is running.
- Setup your environment
- You need to add the Google Maven repository to your project's settings.gradle file in order to include this dependency.
- To include the AndroidX Fragment library to your project, add the following dependencies in your app's build.gradle file
- Create a fragment class
- To create a fragment, extend the AndroidX Fragment class, and override its methods to insert your app logic.
- To create a minimal fragment that defines its own layout, provide your fragment's layout resource to the base constructor.
- For implementation use the link of the header.
- The Fragment library also provides more specialized base classes:
- DialogFragment
- Displays a floating dialog.
- For implementation, read Displaying dialogs with DialogFragment.
- PreferenceFragmentCompat
- Displays a hierarchy of Preference objects as a list.
- You can use PreferenceFragmentCompat to create a settings screen for your app.
- Add a fragment to an activity
- FragmentActivity is the base class for AppCompatActivity, so if you're already subclassing AppCompatActivity to provide backward compatibility in your app, then you do not need to change your activity base class.
- You need to add a FragmentContainerView that defines the location where the fragment should be placed within the activity's view hierarchy. It is strongly recommended to always use a FragmentContainerView as the container for fragments.
- Add a fragment via XML
- To declaratively add a fragment to your activity layout's XML use a FragmentContainerView element.
- For implementation use the link of the header.
- When the activity's layout is inflated, the specified fragment is instantiated, onInflate() is called on the newly instantiated fragment, and a FragmentTransaction is created to add the fragment to the FragmentManager.
- Add a fragment programmatically
- To declaratively add a fragment to your activity layout's XML, use a FragmentContainerView to server as a fragment container.
- The android:name attribute isn't used on the FragmentContainerView here, so no specific fragment is automatically instantiated. Instead, use a FragmentTransaction to instantiate a fragment and add it to the activity's layout.
- While your activity is running, you can make fragment transactions such as adding, removing, or replacing a fragment.
- In your FragmentActivity, you can get an instance of the FragmentManager, which can be used to create a FragmentTransaction.
- Then you can instantiate your fragment within your activity's onCreate() method using FragmentTransaction.add(), passing in the ViewGroup ID of the container in your layout and the fragment class you want to add and then commit the transaction.
- For implementation use the link of the header.
- Note: You should always use setReorderingAllowed(true) when performing a FragmentTransaction.
- To ensure that the fragment is added only once use the condition (savedInstanceState == null)
- If your fragment requires some initial data, arguments can be passed to your fragment by providing a Bundle in the call to FragmentTransaction.add().
- The arguments Bundle can then be retrieved from within your fragment by calling requireArguments() and the appropriate Bundle getter methods can be used to retrieve each argument.
Fragment manager
FragmentManager is the class responsible for performing actions on your app's fragments, such as adding, removing, or replacing them, and adding them to the back stack.
- Access the FragmentManager
- Accessing in an activity
- Every FragmentActivity and subclasses thereof, such as AppCompatActivity, have access to the FragmentManager through getSupportFragmentManager() method.
- Accessing in Fragment
- Fragments are also capable of hosting one or more child fragments. Inside a fragment, you can get a reference to the FragmentManager that manages the fragment's children through getChildFragmentManager(). If you need to access its host FragmentManager, you can use getParentFragmentManager().
- Each host its own FragmentManager associated with it that manages its child fragments.
- Once you have a reference to the FragmentManager, you can use it to manipulate the fragments being displayed to the user.
- Child Fragment
- If you want to show multiple fragments at once, such as in a split-view or a dashboard, you should use child fragments that are managed by your destination fragment and its child fragment manager.
- Using the FragmentManager
- The FragmentManager manages the fragment back stack. At runtime, the FragmentManager, can perform back stack operations like adding or removing fragments in response to user interactions. Each set of changes are committed together as a single unit called a FragmentTransaction.
- When the user presses the Back button on their device, or when you call FragmentManager.popBackStack(), the top-most fragment transaction is popped off of the stack.
- If there are no more fragment transactions on the stack, and if you aren't using child elements, the back event bubbles up to the activity.
- If you are using child fragments, see special considerations for child and sibling fragments.
- When you call addToBackStack() on a transaction, note that the transaction can include any number of operations, such as adding multiple fragments, replacing fragments in multiple containers, and so on.
- If you've committed additional transactions prior to the popBackStack() call, and if you did not use addToBackStack() for the transaction, the Back operations are not reversed.
- Perform a transaction
- To display a fragment within a layout container, use the FragmentManager to create a FragmentTransaction. Within the transaction, you can then perform an add() or replace() operation on the container.
- For implementation use the link of the header.
- setReorderingAllowed(true) optimizes the state changes of the fragments involved in the transaction so that animations and transitions work correctly.
- Calling addToBackStack() commits the transaction to the back stack. The user can later reverse the transaction and bring back the previous fragment by pressing the Back button.
- The optional name provided in the addToBackStack() call gives you the ability to pop back to that specific transaction using popBackStack().
- If you don't call addToBackStack() when you perform a transaction that removes a fragment, then the removed fragment is destroyed when the transaction is committed, and the user cannot navigate back to it. If you do call addToBackStack() when removing a fragment, then the fragment is only STOPPED and is later RESUMED when the user navigates back.
- Find an existing fragment
- You can get a reference to the current fragment withing a layout container by using findFragmentById().
- For implementation use the link of the header.
- You can assign a unique tag to a fragment and get a reference using findFragmentByTag(). You can assign a tag using the android:tag XML attribute on fragments that are defined within your layout, or during an add() or replace() operation within a FragmentTransaction.
- Special considerations for child and sibling fragments
- Only one FragmentManager is allowed to control the fragment back stack at any given time. If your app shows multiple sibling fragments on the screen at the same time, or if your app uses child fragments, then one FragmentManager must be designated to handle your app's primary navigation.
- To define the primary navigation fragment inside of a fragment transaction, call the setPrimaryNavigationFragment() method on the transaction, passing in the instance of the fragment whose childFragmentManager should have primary control.
- Note that when two or more fragments are displayed at the same time, only one of them can be the primary navigation fragment. Setting a fragment as the primary navigation fragment removes the designation from the previous fragment.
- Support multiple back stacks
- FragmentManager allows you to support multiple back stacks with the saveBackStack() and restoreBackStack() methods.
- For implementation use the link of the header.
- You can use saveBackStack() only with the transactions that call setReorderingAllowed(true) to ensure that the transactions can be restored as a single, atomic operation.
- You can call restoreBackStack() with the same name parameter to restore all of the popped transactions and all of the saved fragment states.
- You can't use saveBackStack() and restoreBackStack() unless you pass an optional name for your fragment transactions with addToBackStack().
- Provide dependencies to your fragments
- When adding a fragment, you can instantiate the fragment manually and add it to the FragmentTransaction.
- For implementation use the link of the header.
- When you commit the fragment transaction, the instance of the fragment you created is the instance used. However, during a configuration change, your activity and all of its fragments are destroyed and then recreated with the most applicable Android resources. The FragmentManager handles all of this for you. It recreates instances of your fragments, attaches them to the host, and recreates the back stack state.
- To provide dependencies to your fragment, or to use any custom constructor, you must instead create a custom FragmentFactory subclass and then override FragmentFactory.instantiate. You can then override the FragmentManger's default factory with your custom factory, which is then used to instantiate your fragments.
- For implementation use the header of the subclass.
- Note that setting the FragmentFactory in the activity overrides fragment creation throughout the activity's fragments hierarchy. In other words, the childFragmentManager of any child fragments you add uses the custom fragment factory set here unless overriden at a lower level.
- Testing with FragmentFactory
- For implementation use the link of the header.
Do's and Don'ts
- Even if you are not using Navigation component in your project, your application should follow these design principles.
Comments
Post a Comment