One component in JetPack is Navigation, which as the name implies is a page Navigation component, unlike other third-party Navigation, which is specifically designed for page management in fragments. It is very useful for a single Activity App, because the presentation of an App page based on an Activity is displayed through different fragments. So the management of fragments is critical. It is common for implementations to maintain stack relationships between fragments themselves and be familiar with Fragment transactions. In order to reduce the cost of use and maintenance, the hero of today is Navigation.

If you’re interested in other components of JetPack, I recommend reading my previous series, which is currently the last in the series.

Android Architecture Components Part1:Room

Android Architecture Components Part2:LiveData

Android Architecture Components Part3:Lifecycle

Android Architecture Components Part4:ViewModel

The application of Paging in RecyclerView, this article is enough

From the beginning to the practice of WorkManager, this article is enough

For the use of Navigation, I summarize it into the following four points:

  • Basic configuration of Navigation
  • Navigation jump and data transfer
  • Navigation page animation
  • The Navigation deepLink

configuration

We need to import the Navigation dependency before using it, then we need to create a configuration file for Navigation, which will be located in RES/Navigation /nav_graph.xml. In order to understand the code in this article, I have written a Demo that you can view through the Android highlights.

Open nav_graph.xml in my Demo and you can clearly see the relationship between the pages

There are 6 pages in total. The left is the entry page of the program. The lines between them point to the direction that can be jumped between them.

Let’s take a look at their XM configuration πŸ‘‡

<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/welcome_fragment">
 
    <fragment
        android:id="@+id/welcome_fragment"
        android:name="com.idisfkj.androidapianalysis.navigation.fragment.WelcomeFragment"
        android:label="welcome_fragment"
        tools:layout="@layout/fragment_welcome">
 
        <action
            android:id="@+id/action_go_to_register_page"
            app:destination="@id/register_fragment" />
 
        <action
            android:id="@+id/action_go_to_order_list_page"
            app:destination="@id/order_list_fragment"/>
 
    </fragment>
 
    <fragment
        android:id="@+id/register_fragment"
        android:name="com.idisfkj.androidapianalysis.navigation.fragment.RegisterFragment"
        android:label="register_fragment"
        tools:layout="@layout/fragment_register">
 
        <action
            android:id="@+id/action_go_to_shop_list_page"
            app:destination="@id/shop_list_fragment" />
 
    </fragment>.</navigation>
Copy the code

Page tags include navigation, fragment, and Action

  • Navigation: Defines navigation stack, can be nested, each navigation is independent of each other. It has a property called startDestination that defines the root fragment of the navigation stack
  • Fragment: As the name implies, the fragment page. The associated fragment is defined through the name attribute
  • An action is an Intent to jump to. The target fragment to jump to is associated with destination.

This is the basic configuration of nav_graph.xml.

After configuring it, we also need to associate it with the Activity. Because all fragments can’t live without an Activity.

Navigation provides us with two configuration parameters: defaultNavHost and navGraph, so the following configuration is required in the Activity XML: πŸ‘‡

<?xml version="1.0" encoding="utf-8"? >
<LinearLayout 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"
    android:background="@android:color/background_light"
    android:orientation="vertical"
    tools:context=".navigation.NavigationMainActivity">
 
    <fragment
        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_graph" />
 
</LinearLayout>
Copy the code
  • DefaultNavHost: Intercepts the device rollback operation and gives it to Navigation for management.
  • NavGraph: Navigation configuration file, the nav_graph.xml file we configured above

In addition, the Name attribute of the fragment must be NavHostFragment, because it will act as the manager of all fragments we configure. The NavigationProvider in the internal NavController obtains the NavigationProvider abstract instance of the Navigator. The implementation class is FragmentNavigator, so the navigate method is used to create our configured Fragment. And added to the FrameLayout root layout of NavHostFragment.

At this point, if we run the program directly, we can already see the portal page WelcomeFragment

However, if you click on register and other operations, you will find that clicking on the jump is invalid, so next we need to add the jump to it

jump

Since we already defined actions in nav_graph.xml, jumps are easy to access, with only one line of πŸ‘‡ associated with each action

class WelcomeFragment : Fragment() {
 
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup? , savedInstanceState:Bundle?).: View? {
        return inflater.inflate(R.layout.fragment_welcome, container, false).apply {
            register_bt.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_go_to_register_page))
            stroll_bt.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_go_to_order_list_page))
        }
    }
}
Copy the code

The id in the code is the id of the configured action. The internal principle is to obtain the corresponding NavController first, and then click the view to traverse to find the outermost parent View, because the outermost parent View will not import the configuration file. The onViewCreated method in NavHostFragment is associated with the corresponding NavControllerπŸ‘‡

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        if(! (viewinstanceof ViewGroup)) {
            throw new IllegalStateException("created host view " + view + " is not a ViewGroup");
        }
        Navigation.setViewNavController(view, mNavController);
        // When added programmatically, we need to set the NavController on the parent - i.e.,
        // the View that has the ID matching this NavHostFragment.
        if(view.getParent() ! =null) {
            View rootView = (View) view.getParent();
            if(rootView.getId() == getId()) { Navigation.setViewNavController(rootView, mNavController); }}}Copy the code

Navigate is then called to jump to the page and is eventually Fragment with the FragmentTransaction’s replace (πŸ‘‡)

-------------- NavController ------------------ private void navigate(@NonNull NavDestination node, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) { boolean popped = false; if (navOptions ! = null) { if (navOptions.getPopUpTo() ! = -1) { popped = popBackStackInternal(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive()); } } Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator( node.getNavigatorName()); Bundle finalArgs = node.addInDefaultArgs(args); ---- ------- NavDestination newDest = navigator.navigate(node, finalArgs, navOptions, navigatorExtras); . } -------------- FragmentNavigator ------------------ public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) { if (mFragmentManager.isStateSaved()) { Log.i(TAG, "Ignoring navigate() call: FragmentManager has already" + " saved its state"); return null; } String className = destination.getClassName(); if (className.charAt(0) == '.') { className = mContext.getPackageName() + className; } final Fragment frag = instantiateFragment(mContext, mFragmentManager, className, args); frag.setArguments(args); final FragmentTransaction ft = mFragmentManager.beginTransaction(); int enterAnim = navOptions ! = null ? navOptions.getEnterAnim() : -1; int exitAnim = navOptions ! = null ? navOptions.getExitAnim() : -1; int popEnterAnim = navOptions ! = null ? navOptions.getPopEnterAnim() : -1; int popExitAnim = navOptions ! = null ? navOptions.getPopExitAnim() : -1; if (enterAnim ! = -1 || exitAnim ! = -1 || popEnterAnim ! = -1 || popExitAnim ! = -1) { enterAnim = enterAnim ! = 1? enterAnim : 0; exitAnim = exitAnim ! = 1? exitAnim : 0; popEnterAnim = popEnterAnim ! = 1? popEnterAnim : 0; popExitAnim = popExitAnim ! = 1? popExitAnim : 0; ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim); } key code # -- -- -- -- -- - -- -- -- -- -- - ft. Replace (mContainerId, frag); ft.setPrimaryNavigationFragment(frag); . }Copy the code

NavHostFragment, NavController, NavigatorProvider, FragmentNavigator

The ginseng

The above is the page without reference to jump, so what about with reference to jump?

You’re probably thinking about the bundle, filling the bundle with the data you’re passing. That’s right. The Navigate method provided by the Navigator allows you to pass bundle data πŸ‘‡

findNavController().navigate(R.id.action_go_to_shop_detail_page, bundleOf("title" to "I am title"))
Copy the code

This traditional method does not guarantee consistency in passing data types. In order to reduce manual errors, Navigation provides a Gradle plugin specifically designed to ensure data type safety.

To use it, you need to import the plug-in as follows: πŸ‘‡

buildscript {
    repositories {
        google()
    }
    dependencies {
        def nav_version = "2.1.0."
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"}}Copy the code

Finally, go to build.gradle in app and introduce the plugin πŸ‘‡

apply plugin: "androidx.navigation.safeargs.kotlin"
Copy the code

It’s easy to use, as the parameters need to be configured in nav_graph.xml first. πŸ‘‡

    <fragment
        android:id="@+id/shop_list_fragment"
        android:name="com.idisfkj.androidapianalysis.navigation.fragment.ShopListFragment"
        android:label="shop_list_fragment"
        tools:layout="@layout/fragment_shop_list">
 
        <action
            android:id="@+id/action_go_to_shop_detail_page"
            app:destination="@id/shop_detail_fragment">
 
            <argument
                android:name="title"
                app:argType="string" />
 
        </action>
 
    </fragment>
 
    <fragment
        android:id="@+id/shop_detail_fragment"
        android:name="com.idisfkj.androidapianalysis.navigation.fragment.ShopDetailFragment"
        android:label="shop_detail_fragment"
        tools:layout="@layout/fragment_shop_detail">
 
        <action
            android:id="@+id/action_go_to_cart_page"
            app:destination="@id/cart_fragment"
            app:popUpTo="@id/cart_fragment"
            app:popUpToInclusive="true" />
 
        <argument
            android:name="title"
            app:argType="string" />
 
    </fragment>
Copy the code

ShopListFragment = ShopListFragment = ShopListFragment = ShopListFragment = ShopListFragment = ShopListFragment = ShopListFragment = ShopListFragment = ShopListFragment = ShopListFragment You can also use defaultValue to define a parameter’s defaultValue and nullable to indicate whether it is nullable. The corresponding ShopDetailFragment receiving parameters are the same.

In addition, the popUpTo and popUpToInclusive attributes are used to achieve the SingleTop effect when jumping to CartFragment.

Let’s look directly at how to use these configured parameters in code, first in ShopListFragment πŸ‘‡

holder.item.setOnClickListener(Navigation.createNavigateOnClickListener(ShopListFragmentDirections.actionGoToShopDetailP age(shopList[position])))Copy the code

Or create a createNavigateOnClickListener, only now are no longer jump action id, But by the plugin automatically generated ShopListFragmentDirections. ActionGoToShopDetailPage method. Once the argument argument is configured, the plugin automatically generates a class with [class name]+Directions. The auto-generated class is essentially wrapped with jumps and arguments. The source code is πŸ‘‡

class ShopListFragmentDirections private constructor() {
    private data class ActionGoToShopDetailPage(val title: String) : NavDirections {
        override fun getActionId(a): Int = R.id.action_go_to_shop_detail_page
 
        override fun getArguments(a): Bundle {
            val result = Bundle()
            result.putString("title".this.title)
            return result
        }
    }
 
    companion object {
        fun actionGoToShopDetailPage(title: String): NavDirections = ActionGoToShopDetailPage(title)
    }
}
Copy the code

In essence, the action ID and argument are encapsulated as a NavDirections, which are internally parsed to obtain the action ID and argument to perform the jump.

For the receiving ShopDetailFragment, the plugin page automatically generates a ShopDetailFragmentArgs with [class name]+Args class. So what we need to do is also very simple πŸ‘‡

class ShopDetailFragment : Fragment() {
 
    private val args by navArgs<ShopDetailFragmentArgs>()
 
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup? , savedInstanceState:Bundle?).: View? {
        return inflater.inflate(R.layout.fragment_shop_detail, container, false).apply { title.text = args.title add_cart.setOnClickListener(Navigation.createNavigateOnClickListener(ShopDetailFragmentDirections.actionGoToCartPage())) }}}Copy the code

Use navArgs to get the ShopDetailFragmentArgs object, which contains the passed page data.

animation

In the action, you can not only configure the destination of the jump, but also define the transition animation for the corresponding page, using very simple πŸ‘‡

<?xml version="1.0" encoding="utf-8"? >
<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/welcome_fragment">
 
    <fragment
        android:id="@+id/welcome_fragment"
        android:name="com.idisfkj.androidapianalysis.navigation.fragment.WelcomeFragment"
        android:label="welcome_fragment"
        tools:layout="@layout/fragment_welcome">
 
        <action
            android:id="@+id/action_go_to_register_page"
            app:destination="@id/register_fragment"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_in_left"
            app:popEnterAnim="@anim/slide_out_left"
            app:popExitAnim="@anim/slide_out_right" />
 
        <action
            android:id="@+id/action_go_to_order_list_page"
            app:destination="@id/order_list_fragment"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_in_left"
            app:popEnterAnim="@anim/slide_out_left"
            app:popExitAnim="@anim/slide_out_right" />
 
    </fragment>.</navigation>
Copy the code

There are four animation configuration parameters

  • EnterAnim: Configures target page animation on approach
  • ExitAnim: Configures the original screen animation on approach
  • PopEnterAnim: Configures target page animation for pop rollback
  • PopExitAnim: Configure the original page animation for rolling back pop

Through the above configuration you can see the following effect πŸ‘‡

deepLink

Let’s recall that I need to implement deepLink effects for multiple activities, which should be configured in the Androidmanifest.xml scheme, host, etc. A similar effect is needed for a single Activity, and Navigation also provides a corresponding implementation and is easier to operate.

Navigation provides deepLink tags that can be configured directly in nav_graph.xml, such as πŸ‘‡

    <fragment
        android:id="@+id/register_fragment"
        android:name="com.idisfkj.androidapianalysis.navigation.fragment.RegisterFragment"
        android:label="register_fragment"
        tools:layout="@layout/fragment_register">
 
        <action
            android:id="@+id/action_go_to_shop_list_page"
            app:destination="@id/shop_list_fragment"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_in_left"
            app:popEnterAnim="@anim/slide_out_left"
            app:popExitAnim="@anim/slide_out_right" />
 
        <deepLink app:uri="api://register/" />
 
    </fragment>
Copy the code

DeepLink provides a RegisterFragment page that can jump to the uri. You can also configure parameters to be passed through placeholders, such as πŸ‘‡

<deepLink app:uri="api://register/{id}" />
Copy the code

In this case, we can get the data with the key id in the argument of the registration page.

We need to configure deepLink in androidmanifest.xml and use the nav-graph tag πŸ‘‡ in our Activity

    <application
        .
        android:theme="@style/AppTheme">
        <activity android:name=".navigation.NavigationMainActivity" >
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <nav-graph android:value="@navigation/nav_graph"/>
        </activity>.</application>
Copy the code

Now all you need to do is install the demo on your phone and click the link below

jump to register api

The App will then launch and navigate to the registration screen. Is it very simple?

Finally, we look at the effect πŸ‘‡

This is the end of Navigation for the time being. Through this article, I hope you can get familiar with using Navigation and find the charm of single Activity.

If this article is helpful to you, you can easily like, follow a wave, this is the biggest encouragement to me!

The project address

Android excerption

The purpose of this library is to fully analyze the knowledge points related to Android with detailed Demo, to help readers to grasp and understand the main points explained faster

Android excerption