Home | History | Annotate | Download | only in navigation
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package androidx.navigation;
     18 
     19 import android.content.Context;
     20 import android.content.res.TypedArray;
     21 import android.net.Uri;
     22 import android.os.Bundle;
     23 import android.support.annotation.IdRes;
     24 import android.support.annotation.NonNull;
     25 import android.support.annotation.Nullable;
     26 import android.support.v4.util.Pair;
     27 import android.support.v4.util.SparseArrayCompat;
     28 import android.util.AttributeSet;
     29 
     30 import androidx.navigation.common.R;
     31 
     32 import java.util.Collection;
     33 import java.util.Iterator;
     34 import java.util.NoSuchElementException;
     35 
     36 /**
     37  * NavGraph is a collection of {@link NavDestination} nodes fetchable by ID.
     38  *
     39  * <p>A NavGraph serves as a 'virtual' destination: while the NavGraph itself will not appear
     40  * on the back stack, navigating to the NavGraph will cause the
     41  * {@link #getStartDestination starting destination} to be added to the back stack.</p>
     42  */
     43 public class NavGraph extends NavDestination implements Iterable<NavDestination> {
     44     private final SparseArrayCompat<NavDestination> mNodes = new SparseArrayCompat<>();
     45     private int mStartDestId;
     46 
     47     /**
     48      * Construct a new NavGraph. This NavGraph is not valid until you
     49      * {@link #addDestination(NavDestination) add a destination} and
     50      * {@link #setStartDestination(int) set the starting destination}.
     51      *
     52      * @param navigatorProvider The {@link NavController} which this NavGraph
     53      *                          will be associated with.
     54      */
     55     public NavGraph(@NonNull NavigatorProvider navigatorProvider) {
     56         this(navigatorProvider.getNavigator(NavGraphNavigator.class));
     57     }
     58 
     59     /**
     60      * Construct a new NavGraph. This NavGraph is not valid until you
     61      * {@link #addDestination(NavDestination) add a destination} and
     62      * {@link #setStartDestination(int) set the starting destination}.
     63      *
     64      * @param navGraphNavigator The {@link NavGraphNavigator} which this destination
     65      *                          will be associated with. Generally retrieved via a
     66      *                          {@link NavController}'s
     67      *                          {@link NavigatorProvider#getNavigator(Class)} method.
     68      */
     69     public NavGraph(@NonNull Navigator<? extends NavGraph> navGraphNavigator) {
     70         super(navGraphNavigator);
     71     }
     72 
     73     @Override
     74     public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs) {
     75         super.onInflate(context, attrs);
     76         TypedArray a = context.getResources().obtainAttributes(attrs,
     77                 R.styleable.NavGraphNavigator);
     78         setStartDestination(
     79                 a.getResourceId(R.styleable.NavGraphNavigator_startDestination, 0));
     80         a.recycle();
     81     }
     82 
     83     @Override
     84     @Nullable
     85     Pair<NavDestination, Bundle> matchDeepLink(@NonNull Uri uri) {
     86         // First search through any deep links directly added to this NavGraph
     87         Pair<NavDestination, Bundle> result = super.matchDeepLink(uri);
     88         if (result != null) {
     89             return result;
     90         }
     91         // Then search through all child destinations for a matching deep link
     92         for (NavDestination child : this) {
     93             Pair<NavDestination, Bundle> childResult = child.matchDeepLink(uri);
     94             if (childResult != null) {
     95                 return childResult;
     96             }
     97         }
     98         return null;
     99     }
    100 
    101     /**
    102      * Adds a destination to this NavGraph. The destination must have an
    103      * {@link NavDestination#getId()} id} set.
    104      *
    105      * <p>The destination must not have a {@link NavDestination#getParent() parent} set. If
    106      * the destination is already part of a {@link NavGraph navigation graph}, call
    107      * {@link #remove(NavDestination)} before calling this method.</p>
    108      *
    109      * @param node destination to add
    110      */
    111     public void addDestination(@NonNull NavDestination node) {
    112         if (node.getId() == 0) {
    113             throw new IllegalArgumentException("Destinations must have an id."
    114                     + " Call setId() or include an android:id in your navigation XML.");
    115         }
    116         NavDestination existingDestination = mNodes.get(node.getId());
    117         if (existingDestination == node) {
    118             return;
    119         }
    120         if (node.getParent() != null) {
    121             throw new IllegalStateException("Destination already has a parent set."
    122                     + " Call NavGraph.remove() to remove the previous parent.");
    123         }
    124         if (existingDestination != null) {
    125             existingDestination.setParent(null);
    126         }
    127         node.setParent(this);
    128         mNodes.put(node.getId(), node);
    129     }
    130 
    131     /**
    132      * Adds multiple destinations to this NavGraph. Each destination must have an
    133      * {@link NavDestination#getId()} id} set.
    134      *
    135      * <p> Each destination must not have a {@link NavDestination#getParent() parent} set. If
    136      * any destination is already part of a {@link NavGraph navigation graph}, call
    137      * {@link #remove(NavDestination)} before calling this method.</p>
    138      *
    139      * @param nodes destinations to add
    140      */
    141     public void addDestinations(@NonNull Collection<NavDestination> nodes) {
    142         for (NavDestination node : nodes) {
    143             if (node == null) {
    144                 continue;
    145             }
    146             addDestination(node);
    147         }
    148     }
    149 
    150     /**
    151      * Adds multiple destinations to this NavGraph. Each destination must have an
    152      * {@link NavDestination#getId()} id} set.
    153      *
    154      * <p> Each destination must not have a {@link NavDestination#getParent() parent} set. If
    155      * any destination is already part of a {@link NavGraph navigation graph}, call
    156      * {@link #remove(NavDestination)} before calling this method.</p>
    157      *
    158      * @param nodes destinations to add
    159      */
    160     public void addDestinations(@NonNull NavDestination... nodes) {
    161         for (NavDestination node : nodes) {
    162             if (node == null) {
    163                 continue;
    164             }
    165             addDestination(node);
    166         }
    167     }
    168 
    169     /**
    170      * Finds a destination in the collection by ID. This will recursively check the
    171      * {@link #getParent() parent} of this navigation graph if node is not found in
    172      * this navigation graph.
    173      *
    174      * @param resid ID to locate
    175      * @return the node with ID resid
    176      */
    177     public NavDestination findNode(@IdRes int resid) {
    178         return findNode(resid, true);
    179     }
    180 
    181     NavDestination findNode(@IdRes int resid, boolean searchParents) {
    182         NavDestination destination = mNodes.get(resid);
    183         // Search the parent for the NavDestination if it is not a child of this navigation graph
    184         // and searchParents is true
    185         return destination != null
    186                 ? destination
    187                 : searchParents && getParent() != null ? getParent().findNode(resid) : null;
    188     }
    189 
    190     @NonNull
    191     @Override
    192     public Iterator<NavDestination> iterator() {
    193         return new Iterator<NavDestination>() {
    194             private int mIndex = -1;
    195             private boolean mWentToNext = false;
    196 
    197             @Override
    198             public boolean hasNext() {
    199                 return mIndex + 1 < mNodes.size();
    200             }
    201 
    202             @Override
    203             public NavDestination next() {
    204                 if (!hasNext()) {
    205                     throw new NoSuchElementException();
    206                 }
    207                 mWentToNext = true;
    208                 return mNodes.valueAt(++mIndex);
    209             }
    210 
    211             @Override
    212             public void remove() {
    213                 if (!mWentToNext) {
    214                     throw new IllegalStateException(
    215                             "You must call next() before you can remove an element");
    216                 }
    217                 mNodes.valueAt(mIndex).setParent(null);
    218                 mNodes.removeAt(mIndex);
    219                 mIndex--;
    220                 mWentToNext = false;
    221             }
    222         };
    223     }
    224 
    225     /**
    226      * Add all destinations from another collection to this one. As each destination has at most
    227      * one parent, the destinations will be removed from the given NavGraph.
    228      *
    229      * @param other collection of destinations to add. All destinations will be removed from this
    230      * graph after being added to this graph.
    231      */
    232     public void addAll(@NonNull NavGraph other) {
    233         Iterator<NavDestination> iterator = other.iterator();
    234         while (iterator.hasNext()) {
    235             NavDestination destination = iterator.next();
    236             iterator.remove();
    237             addDestination(destination);
    238         }
    239     }
    240 
    241     /**
    242      * Remove a given destination from this NavGraph
    243      *
    244      * @param node the destination to remove.
    245      */
    246     public void remove(@NonNull NavDestination node) {
    247         int index = mNodes.indexOfKey(node.getId());
    248         if (index >= 0) {
    249             mNodes.valueAt(index).setParent(null);
    250             mNodes.removeAt(index);
    251         }
    252     }
    253 
    254     /**
    255      * Clear all destinations from this navigation graph.
    256      */
    257     public void clear() {
    258         Iterator<NavDestination> iterator = iterator();
    259         while (iterator.hasNext()) {
    260             iterator.next();
    261             iterator.remove();
    262         }
    263     }
    264 
    265     /**
    266      * Returns the starting destination for this NavGraph. When navigating to the NavGraph, this
    267      * destination is the one the user will initially see.
    268      * @return
    269      */
    270     @IdRes
    271     public int getStartDestination() {
    272         return mStartDestId;
    273     }
    274 
    275     /**
    276      * Sets the starting destination for this NavGraph.
    277      *
    278      * @param startDestId The id of the destination to be shown when navigating to this NavGraph.
    279      */
    280     public void setStartDestination(@IdRes int startDestId) {
    281         mStartDestId = startDestId;
    282     }
    283 }
    284