public class

NavDestination

extends java.lang.Object

 java.lang.Object

↳androidx.navigation.NavDestination

Subclasses:

NavGraph, ActivityNavigator.Destination, FragmentNavigator.Destination, DialogFragmentNavigator.Destination

Overview

NavDestination represents one node within an overall navigation graph.

Each destination is associated with a Navigator which knows how to navigate to this particular destination.

Destinations declare a set of actions that they support. These actions form a navigation API for the destination; the same actions declared on different destinations that fill similar roles allow application code to navigate based on semantic intent.

Each destination has a set of arguments that will be applied when navigating to that destination. Any default values for those arguments can be overridden at the time of navigation.

Summary

Constructors
publicNavDestination(Navigator<NavDestination> navigator)

NavDestinations should be created via Navigator.createDestination().

publicNavDestination(java.lang.String navigatorName)

NavDestinations should be created via Navigator.createDestination().

Methods
public final voidaddArgument(java.lang.String argumentName, NavArgument argument)

Sets an argument type for an argument name

public final voidaddDeepLink(NavDeepLink navDeepLink)

Add a deep link to this destination.

public final voidaddDeepLink(java.lang.String uriPattern)

Add a deep link to this destination.

public final NavActiongetAction(int id)

Returns the destination ID for a given action.

public final java.util.Map<java.lang.String, NavArgument>getArguments()

Get the arguments supported by this destination.

public java.lang.StringgetDisplayName()

public final intgetId()

Returns the destination's unique ID.

public final java.lang.CharSequencegetLabel()

Gets the descriptive label of this destination.

public final java.lang.StringgetNavigatorName()

Returns the name associated with this destination's Navigator.

public final NavGraphgetParent()

Gets the NavGraph that contains this destination.

public booleanhasDeepLink(Uri deepLink)

Checks the given deep link , and determines whether it matches a Uri pattern added to the destination by a call to NavDestination.addDeepLink(String) .

public voidonInflate(Context context, AttributeSet attrs)

Called when inflating a destination from a resource.

protected static java.lang.Class<java.lang.Object>parseClassFromName(Context context, java.lang.String name, java.lang.Class<java.lang.Object> expectedClassType)

Parse the class associated with this destination from a raw name, generally extracted from the android:name attribute added to the destination's XML.

public final voidputAction(int actionId, int destId)

Sets a destination ID for an action ID.

public final voidputAction(int actionId, NavAction action)

Sets a destination ID for an action ID.

public final voidremoveAction(int actionId)

Unsets the destination ID for an action ID.

public final voidremoveArgument(java.lang.String argumentName)

Unsets the argument type for an argument name.

public final voidsetId(int id)

Sets the destination's unique ID.

public final voidsetLabel(java.lang.CharSequence label)

Sets the descriptive label of this destination.

public java.lang.StringtoString()

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait

Constructors

public NavDestination(Navigator<NavDestination> navigator)

NavDestinations should be created via Navigator.createDestination().

This constructor requires that the given Navigator has a Navigator.Name annotation.

public NavDestination(java.lang.String navigatorName)

NavDestinations should be created via Navigator.createDestination().

Methods

protected static java.lang.Class<java.lang.Object> parseClassFromName(Context context, java.lang.String name, java.lang.Class<java.lang.Object> expectedClassType)

Parse the class associated with this destination from a raw name, generally extracted from the android:name attribute added to the destination's XML. This should be the class providing the visual representation of the destination that the user sees after navigating to this destination.

This method does name -> Class caching and should be strongly preferred over doing your own parsing if your Navigator supports the android:name attribute to give consistent behavior across all Navigators.

Parameters:

context: Context providing the package name for use with relative class names and the ClassLoader
name: Absolute or relative class name. Null names will be ignored.
expectedClassType: The expected class type

Returns:

The parsed class

public final java.util.Map<java.lang.String, NavArgument> getArguments()

Get the arguments supported by this destination. Returns a read-only map of argument names to NavArgument objects that can be used to check the type, default value and nullability of the argument.

To add and remove arguments for this NavDestination use NavDestination.addArgument(String, NavArgument) and NavDestination.removeArgument(String).

Returns:

Read-only map of argument names to arguments.

public void onInflate(Context context, AttributeSet attrs)

Called when inflating a destination from a resource.

Parameters:

context: local context performing inflation
attrs: attrs to parse during inflation

public final NavGraph getParent()

Gets the NavGraph that contains this destination. This will be set when a destination is added to a NavGraph via NavGraph.addDestination(NavDestination).

Returns:

public final int getId()

Returns the destination's unique ID. This should be an ID resource generated by the Android resource system.

Returns:

this destination's ID

public final void setId(int id)

Sets the destination's unique ID. This should be an ID resource generated by the Android resource system.

Parameters:

id: this destination's new ID

public java.lang.String getDisplayName()

public final void setLabel(java.lang.CharSequence label)

Sets the descriptive label of this destination.

Parameters:

label: A descriptive label of this destination.

public final java.lang.CharSequence getLabel()

Gets the descriptive label of this destination.

public final java.lang.String getNavigatorName()

Returns the name associated with this destination's Navigator.

Returns:

the name associated with this destination's navigator

public boolean hasDeepLink(Uri deepLink)

Checks the given deep link , and determines whether it matches a Uri pattern added to the destination by a call to NavDestination.addDeepLink(String) . It returns true if the deep link is a valid match, and false otherwise.

This should be called prior to NavController.navigate(Uri) to ensure the deep link can be navigated to.

Parameters:

deepLink: to the destination reachable from the current NavGraph

Returns:

True if the deepLink exists for the destination.

See also: NavDestination.addDeepLink(String), NavController.navigate(Uri), NavDestination

public final void addDeepLink(java.lang.String uriPattern)

Add a deep link to this destination. Matching Uris sent to NavController.handleDeepLink(Intent) or NavController.navigate(Uri) will trigger navigating to this destination.

In addition to a direct Uri match, the following features are supported:

  • Uris without a scheme are assumed as http and https. For example, www.example.com will match http://www.example.com and https://www.example.com.
  • Placeholders in the form of {placeholder_name} matches 1 or more characters. The String value of the placeholder will be available in the arguments with a key of the same name. For example, http://www.example.com/users/{id} will match http://www.example.com/users/4.
  • The .* wildcard can be used to match 0 or more characters.
These Uris can be declared in your navigation XML files by adding one or more <deepLink app:uri="uriPattern" /> elements as a child to your destination.

Deep links added in navigation XML files will automatically replace instances of ${applicationId} with the applicationId of your app. Programmatically added deep links should use directly when constructing the uriPattern.

Parameters:

uriPattern: The uri pattern to add as a deep link

See also: NavController.handleDeepLink(Intent), NavController.navigate(Uri), NavDestination.addDeepLink(NavDeepLink)

public final void addDeepLink(NavDeepLink navDeepLink)

Add a deep link to this destination. Uris that match the given NavDeepLink uri sent to NavController.handleDeepLink(Intent) or NavController will trigger navigating to this destination.

In addition to a direct Uri match, the following features are supported:

  • Uris without a scheme are assumed as http and https. For example, www.example.com will match http://www.example.com and https://www.example.com.
  • Placeholders in the form of {placeholder_name} matches 1 or more characters. The String value of the placeholder will be available in the arguments with a key of the same name. For example, http://www.example.com/users/{id} will match http://www.example.com/users/4.
  • The .* wildcard can be used to match 0 or more characters.
These Uris can be declared in your navigation XML files by adding one or more <deepLink app:uri="uriPattern" /> elements as a child to your destination.

Custom actions and mimetypes are also supported by NavDeepLink and can be declared in your navigation XML files by adding <app:action="android.intent.action.SOME_ACTION" /> or <app:mimetype="type/subtype" /> as part of your deepLink declaration.

Deep link Uris, actions, and mimetypes added in navigation XML files will automatically replace instances of ${applicationId} with the applicationId of your app. Programmatically added deep links should use directly when constructing the uriPattern.

When matching deep links for calls to NavController.handleDeepLink(Intent) or NavController the order of precedence is as follows: the deep link with the most matching arguments will be chosen, followed by the deep link with a matching action, followed by the best matching mimeType (e.i. when matching mimeType image/jpg: image/* > *\/jpg > *\/*).

Parameters:

navDeepLink: The NavDeepLink to add as a deep link

See also: NavController.handleDeepLink(Intent), NavController

public final NavAction getAction(int id)

Returns the destination ID for a given action. This will recursively check the parent of this destination if the action destination is not found in this destination.

Parameters:

id: action ID to fetch

Returns:

destination ID mapped to the given action id, or 0 if none

public final void putAction(int actionId, int destId)

Sets a destination ID for an action ID.

Parameters:

actionId: action ID to bind
destId: destination ID for the given action

public final void putAction(int actionId, NavAction action)

Sets a destination ID for an action ID.

Parameters:

actionId: action ID to bind
action: action to associate with this action ID

public final void removeAction(int actionId)

Unsets the destination ID for an action ID.

Parameters:

actionId: action ID to remove

public final void addArgument(java.lang.String argumentName, NavArgument argument)

Sets an argument type for an argument name

Parameters:

argumentName: argument object to associate with destination

public final void removeArgument(java.lang.String argumentName)

Unsets the argument type for an argument name.

Parameters:

argumentName: argument to remove

public java.lang.String toString()

Source

/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.navigation;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.CLASS;

import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.net.Uri;
import android.os.Bundle;
import android.util.AttributeSet;

import androidx.annotation.CallSuper;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.collection.SparseArrayCompat;
import androidx.navigation.common.R;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * NavDestination represents one node within an overall navigation graph.
 *
 * <p>Each destination is associated with a {@link Navigator} which knows how to navigate to this
 * particular destination.</p>
 *
 * <p>Destinations declare a set of {@link #putAction(int, int) actions} that they
 * support. These actions form a navigation API for the destination; the same actions declared
 * on different destinations that fill similar roles allow application code to navigate based
 * on semantic intent.</p>
 *
 * <p>Each destination has a set of {@link #getArguments() arguments} that will
 * be applied when {@link NavController#navigate(int, Bundle) navigating} to that destination.
 * Any default values for those arguments can be overridden at the time of navigation.</p>
 */
public class NavDestination {
    /**
     * This optional annotation allows tooling to offer auto-complete for the
     * <code>android:name</code> attribute. This should match the class type passed to
     * {@link #parseClassFromName(Context, String, Class)} when parsing the
     * <code>android:name</code> attribute.
     */
    @Retention(CLASS)
    @Target({TYPE})
    @SuppressWarnings("UnknownNullness") // TODO https://issuetracker.google.com/issues/112185120
    public @interface ClassType {
        Class<?> value();
    }

    static class DeepLinkMatch implements Comparable<DeepLinkMatch> {
        @NonNull
        private final NavDestination mDestination;
        @Nullable
        private final Bundle mMatchingArgs;
        private final boolean mIsExactDeepLink;

        private final boolean mHasMatchingAction;
        private final int mMimeTypeMatchLevel;

        DeepLinkMatch(@NonNull NavDestination destination, @Nullable Bundle matchingArgs,
                boolean isExactDeepLink, boolean hasMatchingAction, int mimeTypeMatchLevel) {
            mDestination = destination;
            mMatchingArgs = matchingArgs;
            mIsExactDeepLink = isExactDeepLink;
            mHasMatchingAction = hasMatchingAction;
            mMimeTypeMatchLevel = mimeTypeMatchLevel;
        }

        @NonNull
        NavDestination getDestination() {
            return mDestination;
        }

        @Nullable
        Bundle getMatchingArgs() {
            return mMatchingArgs;
        }

        @Override
        public int compareTo(@NonNull DeepLinkMatch other) {
            // Prefer exact deep links
            if (mIsExactDeepLink && !other.mIsExactDeepLink) {
                return 1;
            } else if (!mIsExactDeepLink && other.mIsExactDeepLink) {
                return -1;
            }

            if (mMatchingArgs != null && other.mMatchingArgs == null) {
                return 1;
            } else if (mMatchingArgs == null && other.mMatchingArgs != null) {
                return -1;
            }

            if (mMatchingArgs != null) {
                int sizeDifference = mMatchingArgs.size() - other.mMatchingArgs.size();
                if (sizeDifference > 0) {
                    return 1;
                } else if (sizeDifference < 0) {
                    return -1;
                }
            }

            if (mHasMatchingAction && !other.mHasMatchingAction) {
                return 1;
            } else if (!mHasMatchingAction && other.mHasMatchingAction) {
                return -1;
            }

            return mMimeTypeMatchLevel - other.mMimeTypeMatchLevel;
        }
    }

    private static final HashMap<String, Class<?>> sClasses = new HashMap<>();

    /**
     * Parse the class associated with this destination from a raw name, generally extracted
     * from the <code>android:name</code> attribute added to the destination's XML. This should
     * be the class providing the visual representation of the destination that the
     * user sees after navigating to this destination.
     * <p>
     * This method does name -> Class caching and should be strongly preferred over doing your
     * own parsing if your {@link Navigator} supports the <code>android:name</code> attribute to
     * give consistent behavior across all Navigators.
     *
     * @param context Context providing the package name for use with relative class names and the
     *                ClassLoader
     * @param name Absolute or relative class name. Null names will be ignored.
     * @param expectedClassType The expected class type
     * @return The parsed class
     * @throws IllegalArgumentException if the class is not found in the provided Context's
     * ClassLoader or if the class is not of the expected type
     */
    @SuppressWarnings("unchecked")
    @NonNull
    protected static <C> Class<? extends C> parseClassFromName(@NonNull Context context,
            @NonNull String name,
            @NonNull Class<? extends C> expectedClassType) {
        if (name.charAt(0) == '.') {
            name = context.getPackageName() + name;
        }
        Class<?> clazz = sClasses.get(name);
        if (clazz == null) {
            try {
                clazz = Class.forName(name, true, context.getClassLoader());
                sClasses.put(name, clazz);
            } catch (ClassNotFoundException e) {
                throw new IllegalArgumentException(e);
            }
        }
        if (!expectedClassType.isAssignableFrom(clazz)) {
            throw new IllegalArgumentException(name + " must be a subclass of "
                    + expectedClassType);
        }
        return (Class<? extends C>) clazz;
    }

    /**
     * Retrieve a suitable display name for a given id.
     * @param context Context used to resolve a resource's name
     * @param id The id to get a display name for
     * @return The resource's name if it is a valid id or just the id itself if it is not
     * a valid resource
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    @NonNull
    static String getDisplayName(@NonNull Context context, int id) {
        // aapt-generated IDs have the high byte nonzero,
        // so anything below that cannot be a valid resource id
        if (id <= 0x00FFFFFF) {
            return Integer.toString(id);
        }
        try {
            return context.getResources().getResourceName(id);
        } catch (Resources.NotFoundException e) {
            return Integer.toString(id);
        }
    }

    private final String mNavigatorName;
    private NavGraph mParent;
    private int mId;
    private String mIdName;
    private CharSequence mLabel;
    private ArrayList<NavDeepLink> mDeepLinks;
    private SparseArrayCompat<NavAction> mActions;
    private HashMap<String, NavArgument> mArguments;

    /**
     * Get the arguments supported by this destination. Returns a read-only map of argument names
     * to {@link NavArgument} objects that can be used to check the type, default value
     * and nullability of the argument.
     * <p>
     * To add and remove arguments for this NavDestination
     * use {@link #addArgument(String, NavArgument)} and {@link #removeArgument(String)}.
     * @return Read-only map of argument names to arguments.
     */
    @NonNull
    public final Map<String, NavArgument> getArguments() {
        return mArguments == null ? Collections.<String, NavArgument>emptyMap()
                : Collections.unmodifiableMap(mArguments);
    }

    /**
     * NavDestinations should be created via {@link Navigator#createDestination}.
     * <p>
     * This constructor requires that the given Navigator has a {@link Navigator.Name} annotation.
     */
    public NavDestination(@NonNull Navigator<? extends NavDestination> navigator) {
        this(NavigatorProvider.getNameForNavigator(navigator.getClass()));
    }

    /**
     * NavDestinations should be created via {@link Navigator#createDestination}.
     */
    public NavDestination(@NonNull String navigatorName) {
        mNavigatorName = navigatorName;
    }

    /**
     * Called when inflating a destination from a resource.
     *
     * @param context local context performing inflation
     * @param attrs attrs to parse during inflation
     */
    @CallSuper
    public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs) {
        final TypedArray a = context.getResources().obtainAttributes(attrs,
                R.styleable.Navigator);
        setId(a.getResourceId(R.styleable.Navigator_android_id, 0));
        mIdName = getDisplayName(context, mId);
        setLabel(a.getText(R.styleable.Navigator_android_label));
        a.recycle();
    }

    final void setParent(NavGraph parent) {
        mParent = parent;
    }

    /**
     * Gets the {@link NavGraph} that contains this destination. This will be set when a
     * destination is added to a NavGraph via {@link NavGraph#addDestination}.
     * @return
     */
    @Nullable
    public final NavGraph getParent() {
        return mParent;
    }

    /**
     * Returns the destination's unique ID. This should be an ID resource generated by
     * the Android resource system.
     *
     * @return this destination's ID
     */
    @IdRes
    public final int getId() {
        return mId;
    }

    /**
     * Sets the destination's unique ID. This should be an ID resource generated by
     * the Android resource system.
     *
     * @param id this destination's new ID
     */
    public final void setId(@IdRes int id) {
        mId = id;
        mIdName = null;
    }

    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    @NonNull
    public String getDisplayName() {
        if (mIdName == null) {
            mIdName = Integer.toString(mId);
        }
        return mIdName;
    }

    /**
     * Sets the descriptive label of this destination.
     *
     * @param label A descriptive label of this destination.
     */
    public final void setLabel(@Nullable CharSequence label) {
        mLabel = label;
    }

    /**
     * Gets the descriptive label of this destination.
     */
    @Nullable
    public final CharSequence getLabel() {
        return mLabel;
    }

    /**
     * Returns the name associated with this destination's {@link Navigator}.
     *
     * @return the name associated with this destination's navigator
     */
    @NonNull
    public final String getNavigatorName() {
        return mNavigatorName;
    }

    /**
     * Checks the given deep link {@link Uri}, and determines whether it matches a Uri pattern added
     * to the destination by a call to {@link #addDeepLink(String)} . It returns <code>true</code>
     * if the deep link is a valid match, and <code>false</code> otherwise.
     *
     * <p>
     * This should be called prior to {@link NavController#navigate(Uri)} to ensure the deep link
     * can be navigated to.
     * </p>
     *
     * @param deepLink to the destination reachable from the current NavGraph
     * @return True if the deepLink exists for the destination.
     * @see #addDeepLink(String)
     * @see NavController#navigate(Uri)
     * @see #hasDeepLink(NavDeepLinkRequest)
     */
    public boolean hasDeepLink(@NonNull Uri deepLink) {
        return hasDeepLink(new NavDeepLinkRequest(deepLink, null, null));
    }

    /**
     * Checks the given {@link NavDeepLinkRequest}, and determines whether it matches a
     * {@link NavDeepLink} added to the destination by a call to
     * {@link #addDeepLink(NavDeepLink)}. It returns <code>true</code> if the request is a valid
     * match, and <code>false</code> otherwise.
     *
     * <p>
     * This should be called prior to {@link NavController#navigate(NavDeepLinkRequest)} to
     * ensure the deep link can be navigated to.
     * </p>
     *
     * @param deepLinkRequest to the destination reachable from the current NavGraph
     * @return True if the deepLink exists for the destination.
     * @see #addDeepLink(NavDeepLink)
     * @see NavController#navigate(NavDeepLinkRequest)
     */
    public boolean hasDeepLink(@NonNull NavDeepLinkRequest deepLinkRequest) {
        return matchDeepLink(deepLinkRequest) != null;
    }

    /**
     * Add a deep link to this destination. Matching Uris sent to
     * {@link NavController#handleDeepLink(Intent)} or {@link NavController#navigate(Uri)} will
     * trigger navigating to this destination.
     * <p>
     * In addition to a direct Uri match, the following features are supported:
     * <ul>
     *     <li>Uris without a scheme are assumed as http and https. For example,
     *     <code>www.example.com</code> will match <code>http://www.example.com</code> and
     *     <code>https://www.example.com</code>.</li>
     *     <li>Placeholders in the form of <code>{placeholder_name}</code> matches 1 or more
     *     characters. The String value of the placeholder will be available in the arguments
     *     {@link Bundle} with a key of the same name. For example,
     *     <code>http://www.example.com/users/{id}</code> will match
     *     <code>http://www.example.com/users/4</code>.</li>
     *     <li>The <code>.*</code> wildcard can be used to match 0 or more characters.</li>
     * </ul>
     * These Uris can be declared in your navigation XML files by adding one or more
     * <code>&lt;deepLink app:uri="uriPattern" /&gt;</code> elements as
     * a child to your destination.
     * <p>
     * Deep links added in navigation XML files will automatically replace instances of
     * <code>${applicationId}</code> with the applicationId of your app.
     * Programmatically added deep links should use {@link Context#getPackageName()} directly
     * when constructing the uriPattern.
     * @param uriPattern The uri pattern to add as a deep link
     * @see NavController#handleDeepLink(Intent)
     * @see NavController#navigate(Uri)
     * @see #addDeepLink(NavDeepLink)
     */
    public final void addDeepLink(@NonNull String uriPattern) {
        addDeepLink(new NavDeepLink.Builder().setUriPattern(uriPattern).build());
    }

    /**
     * Add a deep link to this destination. Uris that match the given {@link NavDeepLink} uri
     * sent to {@link NavController#handleDeepLink(Intent)} or
     * {@link NavController#navigate(NavDeepLinkRequest)} will trigger navigating to this
     * destination.
     * <p>
     * In addition to a direct Uri match, the following features are supported:
     * <ul>
     *     <li>Uris without a scheme are assumed as http and https. For example,
     *     <code>www.example.com</code> will match <code>http://www.example.com</code> and
     *     <code>https://www.example.com</code>.</li>
     *     <li>Placeholders in the form of <code>{placeholder_name}</code> matches 1 or more
     *     characters. The String value of the placeholder will be available in the arguments
     *     {@link Bundle} with a key of the same name. For example,
     *     <code>http://www.example.com/users/{id}</code> will match
     *     <code>http://www.example.com/users/4</code>.</li>
     *     <li>The <code>.*</code> wildcard can be used to match 0 or more characters.</li>
     * </ul>
     * These Uris can be declared in your navigation XML files by adding one or more
     * <code>&lt;deepLink app:uri="uriPattern" /&gt;</code> elements as
     * a child to your destination.
     * <p>
     * Custom actions and mimetypes are also supported by {@link NavDeepLink} and can be declared
     * in your navigation XML files by adding
     * <code>&lt;app:action="android.intent.action.SOME_ACTION" /&gt;</code> or
     * <code>&lt;app:mimetype="type/subtype" /&gt;</code> as part of your deepLink declaration.
     * <p>
     * Deep link Uris, actions, and mimetypes added in navigation XML files will automatically
     * replace instances of <code>${applicationId}</code> with the applicationId of your app.
     * Programmatically added deep links should use {@link Context#getPackageName()} directly
     * when constructing the uriPattern.
     * <p>
     * When matching deep links for calls to {@link NavController#handleDeepLink(Intent)} or
     * {@link NavController#navigate(NavDeepLinkRequest)} the order of precedence is as follows:
     * the deep link with the most matching arguments will be chosen, followed by the deep link
     * with a matching action, followed by the best matching mimeType (e.i. when matching
     * mimeType image/jpg: image/* > *\/jpg > *\/*).
     * @param navDeepLink The NavDeepLink to add as a deep link
     * @see NavController#handleDeepLink(Intent)
     * @see NavController#navigate(NavDeepLinkRequest)
     */
    public final void addDeepLink(@NonNull NavDeepLink navDeepLink) {
        if (mDeepLinks == null) {
            mDeepLinks = new ArrayList<>();
        }
        mDeepLinks.add(navDeepLink);
    }

    /**
     * Determines if this NavDestination has a deep link matching the given Uri.
     * @param navDeepLinkRequest The request to match against all deep links added in
     * {@link #addDeepLink(NavDeepLink)}
     * @return The matching {@link NavDestination} and the appropriate {@link Bundle} of arguments
     * extracted from the Uri, or null if no match was found.
     */
    @Nullable
    DeepLinkMatch matchDeepLink(@NonNull NavDeepLinkRequest navDeepLinkRequest) {
        if (mDeepLinks == null) {
            return null;
        }
        DeepLinkMatch bestMatch = null;
        for (NavDeepLink deepLink : mDeepLinks) {
            Uri uri = navDeepLinkRequest.getUri();
            Bundle matchingArguments = uri != null
                    ? deepLink.getMatchingArguments(uri, getArguments()) : null;

            String requestAction = navDeepLinkRequest.getAction();
            boolean matchingAction = requestAction != null && requestAction.equals(
                    deepLink.getAction());

            String mimeType = navDeepLinkRequest.getMimeType();
            int mimeTypeMatchLevel = mimeType != null
                    ? deepLink.getMimeTypeMatchRating(mimeType) : -1;
            if (matchingArguments != null || matchingAction || mimeTypeMatchLevel > -1) {
                DeepLinkMatch newMatch = new DeepLinkMatch(this, matchingArguments,
                        deepLink.isExactDeepLink(), matchingAction, mimeTypeMatchLevel);
                if (bestMatch == null || newMatch.compareTo(bestMatch) > 0) {
                    bestMatch = newMatch;
                }
            }
        }
        return bestMatch;
    }

    /**
     * Build an array containing the hierarchy from the root down to this destination.
     *
     * @return An array containing all of the ids from the root to this destination
     */
    @NonNull
    int[] buildDeepLinkIds() {
        ArrayDeque<NavDestination> hierarchy = new ArrayDeque<>();
        NavDestination current = this;
        do {
            NavGraph parent = current.getParent();
            if (parent == null || parent.getStartDestination() != current.getId()) {
                hierarchy.addFirst(current);
            }
            current = parent;
        } while (current != null);
        int[] deepLinkIds = new int[hierarchy.size()];
        int index = 0;
        for (NavDestination destination : hierarchy) {
            deepLinkIds[index++] = destination.getId();
        }
        return deepLinkIds;
    }

    /**
     * @return Whether this NavDestination supports outgoing actions
     * @see #putAction(int, NavAction)
     */
    boolean supportsActions() {
        return true;
    }

    /**
     * Returns the destination ID for a given action. This will recursively check the
     * {@link #getParent() parent} of this destination if the action destination is not found in
     * this destination.
     *
     * @param id action ID to fetch
     * @return destination ID mapped to the given action id, or 0 if none
     */
    @Nullable
    public final NavAction getAction(@IdRes int id) {
        NavAction destination = mActions == null ? null : mActions.get(id);
        // Search the parent for the given action if it is not found in this destination
        return destination != null
                ? destination
                : getParent() != null ? getParent().getAction(id) : null;
    }

    /**
     * Sets a destination ID for an action ID.
     *
     * @param actionId action ID to bind
     * @param destId destination ID for the given action
     */
    public final void putAction(@IdRes int actionId, @IdRes int destId) {
        putAction(actionId, new NavAction(destId));
    }

    /**
     * Sets a destination ID for an action ID.
     *
     * @param actionId action ID to bind
     * @param action action to associate with this action ID
     */
    public final void putAction(@IdRes int actionId, @NonNull NavAction action) {
        if (!supportsActions()) {
            throw new UnsupportedOperationException("Cannot add action " + actionId + " to "
                    + this + " as it does not support actions, indicating that it is a "
                    + "terminal destination in your navigation graph and will never trigger "
                    + "actions.");
        }
        if (actionId == 0) {
            throw new IllegalArgumentException("Cannot have an action with actionId 0");
        }
        if (mActions == null) {
            mActions = new SparseArrayCompat<>();
        }
        mActions.put(actionId, action);
    }

    /**
     * Unsets the destination ID for an action ID.
     *
     * @param actionId action ID to remove
     */
    public final void removeAction(@IdRes int actionId) {
        if (mActions == null) {
            return;
        }
        mActions.remove(actionId);
    }

    /**
     * Sets an argument type for an argument name
     *
     * @param argumentName argument object to associate with destination
     */
    public final void addArgument(@NonNull String argumentName, @NonNull NavArgument argument) {
        if (mArguments == null) {
            mArguments = new HashMap<>();
        }
        mArguments.put(argumentName, argument);
    }

    /**
     * Unsets the argument type for an argument name.
     *
     * @param argumentName argument to remove
     */
    public final void removeArgument(@NonNull String argumentName) {
        if (mArguments == null) {
            return;
        }
        mArguments.remove(argumentName);
    }

    /**
     * Combines the default arguments for this destination with the arguments provided
     * to construct the final set of arguments that should be used to navigate
     * to this destination.
     */
    @Nullable
    Bundle addInDefaultArgs(@Nullable Bundle args) {
        if (args == null && (mArguments == null || mArguments.isEmpty())) {
            return null;
        }
        Bundle defaultArgs = new Bundle();
        if (mArguments != null) {
            for (Map.Entry<String, NavArgument> argument : mArguments.entrySet()) {
                argument.getValue().putDefaultValue(argument.getKey(), defaultArgs);
            }
        }
        if (args != null) {
            defaultArgs.putAll(args);
            if (mArguments != null) {
                for (Map.Entry<String, NavArgument> argument : mArguments.entrySet()) {
                    if (!argument.getValue().verify(argument.getKey(), args)) {
                        throw new IllegalArgumentException(
                                "Wrong argument type for '" + argument.getKey()
                                        + "' in argument bundle. "
                                        + argument.getValue().getType().getName() + " expected.");
                    }
                }
            }
        }
        return defaultArgs;
    }

    @NonNull
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append("(");
        if (mIdName == null) {
            sb.append("0x");
            sb.append(Integer.toHexString(mId));
        } else {
            sb.append(mIdName);
        }
        sb.append(")");
        if (mLabel != null) {
            sb.append(" label=");
            sb.append(mLabel);
        }
        return sb.toString();
    }
}