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.

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.

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

 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.

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.
 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.

    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
    • 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.

    Reference

    Comments

    Popular posts from this blog

    SSLSocketFactory in Android

    Architecture Components in Android

    DataBinding in Android