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 17package androidx.navigation; 18 19import android.app.Activity; 20import android.content.Context; 21import android.content.ContextWrapper; 22import android.content.Intent; 23import android.os.Bundle; 24import android.support.annotation.IdRes; 25import android.support.annotation.NavigationRes; 26import android.support.annotation.NonNull; 27import android.support.annotation.Nullable; 28import android.support.v4.app.TaskStackBuilder; 29import android.support.v4.util.Pair; 30 31import java.util.ArrayDeque; 32import java.util.ArrayList; 33import java.util.Deque; 34import java.util.Iterator; 35import java.util.Map; 36import java.util.concurrent.CopyOnWriteArrayList; 37 38/** 39 * NavController manages app navigation within a {@link NavHost}. 40 * 41 * <p>Apps will generally obtain a controller directly from a host, or by using one of the utility 42 * methods on the {@link Navigation} class rather than create a controller directly.</p> 43 * 44 * <p>Navigation flows and destinations are determined by the 45 * {@link NavGraph navigation graph} owned by the controller. These graphs are typically 46 * {@link #getNavInflater() inflated} from an Android resource, but, like views, they can also 47 * be constructed or combined programmatically or for the case of dynamic navigation structure. 48 * (For example, if the navigation structure of the application is determined by live data obtained' 49 * from a remote server.)</p> 50 */ 51public class NavController { 52 private static final String KEY_NAVIGATOR_STATE = 53 "android-support-nav:controller:navigatorState"; 54 private static final String KEY_NAVIGATOR_STATE_NAMES = 55 "android-support-nav:controller:navigatorState:names"; 56 private static final String KEY_GRAPH_ID = "android-support-nav:controller:graphId"; 57 private static final String KEY_BACK_STACK_IDS = "android-support-nav:controller:backStackIds"; 58 static final String KEY_DEEP_LINK_IDS = "android-support-nav:controller:deepLinkIds"; 59 static final String KEY_DEEP_LINK_EXTRAS = 60 "android-support-nav:controller:deepLinkExtras"; 61 /** 62 * The {@link Intent} that triggered a deep link to the current destination. 63 */ 64 public static final String KEY_DEEP_LINK_INTENT = 65 "android-support-nav:controller:deepLinkIntent"; 66 67 private final Context mContext; 68 private Activity mActivity; 69 private NavInflater mInflater; 70 private NavGraph mGraph; 71 private int mGraphId; 72 private Bundle mNavigatorStateToRestore; 73 private int[] mBackStackToRestore; 74 75 private final Deque<NavDestination> mBackStack = new ArrayDeque<>(); 76 77 private final SimpleNavigatorProvider mNavigatorProvider = new SimpleNavigatorProvider() { 78 @Nullable 79 @Override 80 public Navigator<? extends NavDestination> addNavigator(@NonNull String name, 81 @NonNull Navigator<? extends NavDestination> navigator) { 82 Navigator<? extends NavDestination> previousNavigator = 83 super.addNavigator(name, navigator); 84 if (previousNavigator != navigator) { 85 if (previousNavigator != null) { 86 previousNavigator.removeOnNavigatorNavigatedListener(mOnNavigatedListener); 87 } 88 navigator.addOnNavigatorNavigatedListener(mOnNavigatedListener); 89 } 90 return previousNavigator; 91 } 92 }; 93 94 private final Navigator.OnNavigatorNavigatedListener mOnNavigatedListener = 95 new Navigator.OnNavigatorNavigatedListener() { 96 @Override 97 public void onNavigatorNavigated(@NonNull Navigator navigator, @IdRes int destId, 98 @Navigator.BackStackEffect int backStackEffect) { 99 if (destId != 0) { 100 // First remove popped destinations off the back stack 101 if (backStackEffect == Navigator.BACK_STACK_DESTINATION_POPPED) { 102 while (!mBackStack.isEmpty() 103 && mBackStack.peekLast().getId() != destId) { 104 mBackStack.removeLast(); 105 } 106 } 107 NavDestination newDest = findDestination(destId); 108 if (newDest == null) { 109 throw new IllegalArgumentException("Navigator " + navigator 110 + " reported navigation to unknown destination id " 111 + NavDestination.getDisplayName(mContext, destId)); 112 } 113 if (backStackEffect == Navigator.BACK_STACK_DESTINATION_ADDED) { 114 // Add the new destination to the back stack 115 mBackStack.add(newDest); 116 } 117 // Don't dispatchOnNavigated if nothing changed 118 if (backStackEffect != Navigator.BACK_STACK_UNCHANGED) { 119 dispatchOnNavigated(newDest); 120 } 121 } 122 } 123 }; 124 125 private final CopyOnWriteArrayList<OnNavigatedListener> mOnNavigatedListeners = 126 new CopyOnWriteArrayList<>(); 127 128 /** 129 * OnNavigatorNavigatedListener receives a callback when the associated controller 130 * navigates to a new destination. 131 */ 132 public interface OnNavigatedListener { 133 /** 134 * onNavigatorNavigated is called when the controller navigates to a new destination. 135 * This navigation may be to a destination that has not been seen before, or one that 136 * was previously on the back stack. This method is called after navigation is complete, 137 * but associated transitions may still be playing. 138 * 139 * @param controller the controller that navigated 140 * @param destination the new destination 141 */ 142 void onNavigated(@NonNull NavController controller, @NonNull NavDestination destination); 143 } 144 145 /** 146 * Constructs a new controller for a given {@link Context}. Controllers should not be 147 * used outside of their context and retain a hard reference to the context supplied. 148 * If you need a global controller, pass {@link Context#getApplicationContext()}. 149 * 150 * <p>Apps should generally not construct controllers, instead obtain a relevant controller 151 * directly from a navigation host via {@link NavHost#getNavController()} or by using one of 152 * the utility methods on the {@link Navigation} class.</p> 153 * 154 * <p>Note that controllers that are not constructed with an {@link Activity} context 155 * (or a wrapped activity context) will only be able to navigate to 156 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK new tasks} or 157 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT new document tasks} when 158 * navigating to new activities.</p> 159 * 160 * @param context context for this controller 161 */ 162 public NavController(@NonNull Context context) { 163 mContext = context; 164 while (context instanceof ContextWrapper) { 165 if (context instanceof Activity) { 166 mActivity = (Activity) context; 167 break; 168 } 169 context = ((ContextWrapper) context).getBaseContext(); 170 } 171 mNavigatorProvider.addNavigator(new NavGraphNavigator(mContext)); 172 mNavigatorProvider.addNavigator(new ActivityNavigator(mContext)); 173 } 174 175 @NonNull 176 Context getContext() { 177 return mContext; 178 } 179 180 /** 181 * Retrieve the NavController's {@link NavigatorProvider}. All {@link Navigator Navigators} used 182 * to construct the {@link NavGraph navigation graph} for this nav controller should be added 183 * to this navigator provider before the graph is constructed. 184 * <p> 185 * Generally, the Navigators are set for you by the {@link NavHost} hosting this NavController 186 * and you do not need to manually interact with the navigator provider. 187 * </p> 188 * @return The {@link NavigatorProvider} used by this NavController. 189 */ 190 @NonNull 191 public NavigatorProvider getNavigatorProvider() { 192 return mNavigatorProvider; 193 } 194 195 /** 196 * Adds an {@link OnNavigatedListener} to this controller to receive events when 197 * the controller navigates to a new destination. 198 * 199 * <p>The current destination, if any, will be immediately sent to your listener.</p> 200 * 201 * @param listener the listener to receive events 202 */ 203 public void addOnNavigatedListener(@NonNull OnNavigatedListener listener) { 204 // Inform the new listener of our current state, if any 205 if (!mBackStack.isEmpty()) { 206 listener.onNavigated(this, mBackStack.peekLast()); 207 } 208 mOnNavigatedListeners.add(listener); 209 } 210 211 /** 212 * Removes an {@link OnNavigatedListener} from this controller. It will no longer 213 * receive navigation events. 214 * 215 * @param listener the listener to remove 216 */ 217 public void removeOnNavigatedListener(@NonNull OnNavigatedListener listener) { 218 mOnNavigatedListeners.remove(listener); 219 } 220 221 /** 222 * Attempts to pop the controller's back stack. Analogous to when the user presses 223 * the system {@link android.view.KeyEvent#KEYCODE_BACK Back} button when the associated 224 * navigation host has focus. 225 * 226 * @return true if the stack was popped, false otherwise 227 */ 228 public boolean popBackStack() { 229 if (mBackStack.isEmpty()) { 230 throw new IllegalArgumentException("NavController back stack is empty"); 231 } 232 boolean popped = false; 233 while (!mBackStack.isEmpty()) { 234 popped = mBackStack.removeLast().getNavigator().popBackStack(); 235 if (popped) { 236 break; 237 } 238 } 239 return popped; 240 } 241 242 243 /** 244 * Attempts to pop the controller's back stack back to a specific destination. 245 * 246 * @param destinationId The topmost destination to retain 247 * @param inclusive Whether the given destination should also be popped. 248 * 249 * @return true if the stack was popped at least once, false otherwise 250 */ 251 public boolean popBackStack(@IdRes int destinationId, boolean inclusive) { 252 if (mBackStack.isEmpty()) { 253 throw new IllegalArgumentException("NavController back stack is empty"); 254 } 255 ArrayList<NavDestination> destinationsToRemove = new ArrayList<>(); 256 Iterator<NavDestination> iterator = mBackStack.descendingIterator(); 257 while (iterator.hasNext()) { 258 NavDestination destination = iterator.next(); 259 if (inclusive || destination.getId() != destinationId) { 260 destinationsToRemove.add(destination); 261 } 262 if (destination.getId() == destinationId) { 263 break; 264 } 265 } 266 boolean popped = false; 267 iterator = destinationsToRemove.iterator(); 268 while (iterator.hasNext()) { 269 NavDestination destination = iterator.next(); 270 // Skip destinations already removed by a previous popBackStack operation 271 while (!mBackStack.isEmpty() && mBackStack.peekLast().getId() != destination.getId()) { 272 if (iterator.hasNext()) { 273 destination = iterator.next(); 274 } else { 275 destination = null; 276 break; 277 } 278 } 279 if (destination != null) { 280 popped = destination.getNavigator().popBackStack() || popped; 281 } 282 } 283 return popped; 284 } 285 286 /** 287 * Attempts to navigate up in the navigation hierarchy. Suitable for when the 288 * user presses the "Up" button marked with a left (or start)-facing arrow in the upper left 289 * (or starting) corner of the app UI. 290 * 291 * <p>The intended behavior of Up differs from {@link #popBackStack() Back} when the user 292 * did not reach the current destination from the application's own task. e.g. if the user 293 * is viewing a document or link in the current app in an activity hosted on another app's 294 * task where the user clicked the link. In this case the current activity (determined by the 295 * context used to create this NavController) will be {@link Activity#finish() finished} and 296 * the user will be taken to an appropriate destination in this app on its own task.</p> 297 * 298 * @return true if navigation was successful, false otherwise 299 */ 300 public boolean navigateUp() { 301 if (mBackStack.size() == 1) { 302 // If there's only one entry, then we've deep linked into a specific destination 303 // on another task so we need to find the parent and start our task from there 304 NavDestination currentDestination = getCurrentDestination(); 305 int destId = currentDestination.getId(); 306 NavGraph parent = currentDestination.getParent(); 307 while (parent != null) { 308 if (parent.getStartDestination() != destId) { 309 TaskStackBuilder parentIntents = new NavDeepLinkBuilder(NavController.this) 310 .setDestination(parent.getId()) 311 .createTaskStackBuilder(); 312 parentIntents.startActivities(); 313 if (mActivity != null) { 314 mActivity.finish(); 315 } 316 return true; 317 } 318 destId = parent.getId(); 319 parent = parent.getParent(); 320 } 321 // We're already at the startDestination of the graph so there's no 'Up' to go to 322 return false; 323 } else { 324 return popBackStack(); 325 } 326 } 327 328 private void dispatchOnNavigated(NavDestination destination) { 329 for (OnNavigatedListener listener : mOnNavigatedListeners) { 330 listener.onNavigated(this, destination); 331 } 332 } 333 334 /** 335 * Sets the {@link NavGraph navigation graph} as specified in the application manifest. 336 * 337 * <p>Applications may declare a graph resource in their manifest instead of declaring 338 * or passing this data to each host or controller:</p> 339 * 340 * <pre class="prettyprint"> 341 * <meta-data android:name="android.nav.graph" android:resource="@xml/my_nav_graph" /> 342 * </pre> 343 * 344 * <p>The inflated graph can be retrieved via {@link #getGraph()}. Calling this will have no 345 * effect if there is no metadata graph specified.</p> 346 * 347 * @see NavInflater#METADATA_KEY_GRAPH 348 * @see NavInflater#inflateMetadataGraph() 349 * @see #getGraph 350 */ 351 public void setMetadataGraph() { 352 NavGraph metadataGraph = getNavInflater().inflateMetadataGraph(); 353 if (metadataGraph != null) { 354 setGraph(metadataGraph); 355 } 356 } 357 358 /** 359 * Returns the {@link NavInflater inflater} for this controller. 360 * 361 * @return inflater for loading navigation resources 362 */ 363 @NonNull 364 public NavInflater getNavInflater() { 365 if (mInflater == null) { 366 mInflater = new NavInflater(mContext, mNavigatorProvider); 367 } 368 return mInflater; 369 } 370 371 /** 372 * Sets the {@link NavGraph navigation graph} to the specified resource. 373 * Any current navigation graph data will be replaced. 374 * 375 * <p>The inflated graph can be retrieved via {@link #getGraph()}.</p> 376 * 377 * @param graphResId resource id of the navigation graph to inflate 378 * 379 * @see #getNavInflater() 380 * @see #setGraph(NavGraph) 381 * @see #getGraph 382 */ 383 public void setGraph(@NavigationRes int graphResId) { 384 mGraph = getNavInflater().inflate(graphResId); 385 mGraphId = graphResId; 386 onGraphCreated(); 387 } 388 389 /** 390 * Sets the {@link NavGraph navigation graph} to the specified graph. 391 * Any current navigation graph data will be replaced. 392 * 393 * <p>The graph can be retrieved later via {@link #getGraph()}.</p> 394 * 395 * @param graph graph to set 396 * @see #setGraph(int) 397 * @see #getGraph 398 */ 399 public void setGraph(@NonNull NavGraph graph) { 400 mGraph = graph; 401 mGraphId = 0; 402 onGraphCreated(); 403 } 404 405 private void onGraphCreated() { 406 if (mNavigatorStateToRestore != null) { 407 ArrayList<String> navigatorNames = mNavigatorStateToRestore.getStringArrayList( 408 KEY_NAVIGATOR_STATE_NAMES); 409 if (navigatorNames != null) { 410 for (String name : navigatorNames) { 411 Navigator navigator = mNavigatorProvider.getNavigator(name); 412 Bundle bundle = mNavigatorStateToRestore.getBundle(name); 413 if (bundle != null) { 414 navigator.onRestoreState(bundle); 415 } 416 } 417 } 418 } 419 if (mBackStackToRestore != null) { 420 for (int destinationId : mBackStackToRestore) { 421 NavDestination node = findDestination(destinationId); 422 if (node == null) { 423 throw new IllegalStateException("unknown destination during restore: " 424 + mContext.getResources().getResourceName(destinationId)); 425 } 426 mBackStack.add(node); 427 } 428 mBackStackToRestore = null; 429 } 430 if (mGraph != null && mBackStack.isEmpty()) { 431 boolean deepLinked = mActivity != null && onHandleDeepLink(mActivity.getIntent()); 432 if (!deepLinked) { 433 // Navigate to the first destination in the graph 434 // if we haven't deep linked to a destination 435 mGraph.navigate(null, null); 436 } 437 } 438 } 439 440 /** 441 * Checks the given Intent for a Navigation deep link and navigates to the deep link if present. 442 * This is called automatically for you the first time you set the graph if you've passed in an 443 * {@link Activity} as the context when constructing this NavController, but should be manually 444 * called if your Activity receives new Intents in {@link Activity#onNewIntent(Intent)}. 445 * <p> 446 * The types of Intents that are supported include: 447 * <ul> 448 * <ol>Intents created by {@link NavDeepLinkBuilder} or 449 * {@link #createDeepLink()}. This assumes that the current graph shares 450 * the same hierarchy to get to the deep linked destination as when the deep link was 451 * constructed.</ol> 452 * <ol>Intents that include a {@link Intent#getData() data Uri}. This Uri will be checked 453 * against the Uri patterns added via {@link NavDestination#addDeepLink(String)}.</ol> 454 * </ul> 455 * <p>The {@link #getGraph() navigation graph} should be set before calling this method.</p> 456 * @param intent The Intent that may contain a valid deep link 457 * @return True if the navigation controller found a valid deep link and navigated to it. 458 * @see NavDestination#addDeepLink(String) 459 */ 460 public boolean onHandleDeepLink(@Nullable Intent intent) { 461 if (intent == null) { 462 return false; 463 } 464 Bundle extras = intent.getExtras(); 465 int[] deepLink = extras != null ? extras.getIntArray(KEY_DEEP_LINK_IDS) : null; 466 Bundle bundle = new Bundle(); 467 Bundle deepLinkExtras = extras != null ? extras.getBundle(KEY_DEEP_LINK_EXTRAS) : null; 468 if (deepLinkExtras != null) { 469 bundle.putAll(deepLinkExtras); 470 } 471 if ((deepLink == null || deepLink.length == 0) && intent.getData() != null) { 472 Pair<NavDestination, Bundle> matchingDeepLink = mGraph.matchDeepLink(intent.getData()); 473 if (matchingDeepLink != null) { 474 deepLink = matchingDeepLink.first.buildDeepLinkIds(); 475 bundle.putAll(matchingDeepLink.second); 476 } 477 } 478 if (deepLink == null || deepLink.length == 0) { 479 return false; 480 } 481 bundle.putParcelable(KEY_DEEP_LINK_INTENT, intent); 482 int flags = intent.getFlags(); 483 if ((flags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0 484 && (flags & Intent.FLAG_ACTIVITY_CLEAR_TASK) == 0) { 485 // Someone called us with NEW_TASK, but we don't know what state our whole 486 // task stack is in, so we need to manually restart the whole stack to 487 // ensure we're in a predictably good state. 488 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); 489 TaskStackBuilder taskStackBuilder = TaskStackBuilder 490 .create(mContext) 491 .addNextIntentWithParentStack(intent); 492 taskStackBuilder.startActivities(); 493 if (mActivity != null) { 494 mActivity.finish(); 495 } 496 return true; 497 } 498 if ((flags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { 499 // Start with a cleared task starting at our root when we're on our own task 500 if (!mBackStack.isEmpty()) { 501 navigate(mGraph.getStartDestination(), bundle, new NavOptions.Builder() 502 .setPopUpTo(mGraph.getId(), true) 503 .setEnterAnim(0).setExitAnim(0).build()); 504 } 505 int index = 0; 506 while (index < deepLink.length) { 507 int destinationId = deepLink[index++]; 508 NavDestination node = findDestination(destinationId); 509 if (node == null) { 510 throw new IllegalStateException("unknown destination during deep link: " 511 + NavDestination.getDisplayName(mContext, destinationId)); 512 } 513 node.navigate(bundle, 514 new NavOptions.Builder().setEnterAnim(0).setExitAnim(0).build()); 515 } 516 return true; 517 } 518 // Assume we're on another apps' task and only start the final destination 519 NavGraph graph = mGraph; 520 for (int i = 0; i < deepLink.length; i++) { 521 int destinationId = deepLink[i]; 522 NavDestination node = i == 0 ? mGraph : graph.findNode(destinationId); 523 if (node == null) { 524 throw new IllegalStateException("unknown destination during deep link: " 525 + NavDestination.getDisplayName(mContext, destinationId)); 526 } 527 if (i != deepLink.length - 1) { 528 // We're not at the final NavDestination yet, so keep going through the chain 529 graph = (NavGraph) node; 530 } else { 531 // Navigate to the last NavDestination, clearing any existing destinations 532 node.navigate(bundle, new NavOptions.Builder() 533 .setPopUpTo(mGraph.getId(), true) 534 .setEnterAnim(0).setExitAnim(0).build()); 535 } 536 } 537 return true; 538 } 539 540 /** 541 * Gets the topmost navigation graph associated with this NavController. 542 * 543 * @see #setGraph(int) 544 * @see #setGraph(NavGraph) 545 * @see #setMetadataGraph() 546 */ 547 public NavGraph getGraph() { 548 return mGraph; 549 } 550 551 /** 552 * Gets the current destination. 553 */ 554 public NavDestination getCurrentDestination() { 555 return mBackStack.peekLast(); 556 } 557 558 private NavDestination findDestination(@IdRes int destinationId) { 559 if (mGraph == null) { 560 return null; 561 } 562 if (mGraph.getId() == destinationId) { 563 return mGraph; 564 } 565 NavDestination currentNode = mBackStack.isEmpty() ? mGraph : mBackStack.peekLast(); 566 NavGraph currentGraph = currentNode instanceof NavGraph 567 ? (NavGraph) currentNode 568 : currentNode.getParent(); 569 return currentGraph.findNode(destinationId); 570 } 571 572 /** 573 * Navigate to a destination from the current navigation graph. This supports both navigating 574 * via an {@link NavDestination#getAction(int) action} and directly navigating to a destination. 575 * 576 * @param resId an {@link NavDestination#getAction(int) action} id or a destination id to 577 * navigate to 578 */ 579 public final void navigate(@IdRes int resId) { 580 navigate(resId, null); 581 } 582 583 /** 584 * Navigate to a destination from the current navigation graph. This supports both navigating 585 * via an {@link NavDestination#getAction(int) action} and directly navigating to a destination. 586 * 587 * @param resId an {@link NavDestination#getAction(int) action} id or a destination id to 588 * navigate to 589 * @param args arguments to pass to the destination 590 */ 591 public final void navigate(@IdRes int resId, @Nullable Bundle args) { 592 navigate(resId, args, null); 593 } 594 595 /** 596 * Navigate to a destination from the current navigation graph. This supports both navigating 597 * via an {@link NavDestination#getAction(int) action} and directly navigating to a destination. 598 * 599 * @param resId an {@link NavDestination#getAction(int) action} id or a destination id to 600 * navigate to 601 * @param args arguments to pass to the destination 602 * @param navOptions special options for this navigation operation 603 */ 604 @SuppressWarnings("deprecation") 605 public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions) { 606 NavDestination currentNode = mBackStack.isEmpty() ? mGraph : mBackStack.peekLast(); 607 if (currentNode == null) { 608 throw new IllegalStateException("no current navigation node"); 609 } 610 @IdRes int destId = resId; 611 final NavAction navAction = currentNode.getAction(resId); 612 if (navAction != null) { 613 if (navOptions == null) { 614 navOptions = navAction.getNavOptions(); 615 } 616 destId = navAction.getDestinationId(); 617 } 618 if (destId == 0 && navOptions != null && navOptions.getPopUpTo() != 0) { 619 popBackStack(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive()); 620 return; 621 } 622 623 if (destId == 0) { 624 throw new IllegalArgumentException("Destination id == 0 can only be used" 625 + " in conjunction with navOptions.popUpTo != 0"); 626 } 627 628 NavDestination node = findDestination(destId); 629 if (node == null) { 630 final String dest = NavDestination.getDisplayName(mContext, destId); 631 throw new IllegalArgumentException("navigation destination " + dest 632 + (navAction != null 633 ? " referenced from action " + NavDestination.getDisplayName(mContext, resId) 634 : "") 635 + " is unknown to this NavController"); 636 } 637 if (navOptions != null) { 638 if (navOptions.shouldClearTask()) { 639 // Start with a clean slate 640 popBackStack(mGraph.getId(), true); 641 } else if (navOptions.getPopUpTo() != 0) { 642 popBackStack(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive()); 643 } 644 } 645 node.navigate(args, navOptions); 646 } 647 648 /** 649 * Navigate via the given {@link NavDirections} 650 * 651 * @param directions directions that describe this navigation operation 652 */ 653 public void navigate(@NonNull NavDirections directions) { 654 navigate(directions.getActionId(), directions.getArguments()); 655 } 656 657 /** 658 * Navigate via the given {@link NavDirections} 659 * 660 * @param directions directions that describe this navigation operation 661 */ 662 public void navigate(@NonNull NavDirections directions, @Nullable NavOptions navOptions) { 663 navigate(directions.getActionId(), directions.getArguments(), navOptions); 664 } 665 /** 666 * Create a deep link to a destination within this NavController. 667 * 668 * @return a {@link NavDeepLinkBuilder} suitable for constructing a deep link 669 */ 670 @NonNull 671 public NavDeepLinkBuilder createDeepLink() { 672 return new NavDeepLinkBuilder(this); 673 } 674 675 /** 676 * Saves all navigation controller state to a Bundle. 677 * 678 * <p>State may be restored from a bundle returned from this method by calling 679 * {@link #restoreState(Bundle)}. Saving controller state is the responsibility 680 * of a {@link NavHost}.</p> 681 * 682 * @return saved state for this controller 683 */ 684 @Nullable 685 public Bundle saveState() { 686 Bundle b = null; 687 if (mGraphId != 0) { 688 b = new Bundle(); 689 b.putInt(KEY_GRAPH_ID, mGraphId); 690 } 691 ArrayList<String> navigatorNames = new ArrayList<>(); 692 Bundle navigatorState = new Bundle(); 693 for (Map.Entry<String, Navigator<? extends NavDestination>> entry : 694 mNavigatorProvider.getNavigators().entrySet()) { 695 String name = entry.getKey(); 696 Bundle savedState = entry.getValue().onSaveState(); 697 if (savedState != null) { 698 navigatorNames.add(name); 699 navigatorState.putBundle(name, entry.getValue().onSaveState()); 700 } 701 } 702 if (!navigatorNames.isEmpty()) { 703 if (b == null) { 704 b = new Bundle(); 705 } 706 navigatorState.putStringArrayList(KEY_NAVIGATOR_STATE_NAMES, navigatorNames); 707 b.putBundle(KEY_NAVIGATOR_STATE, navigatorState); 708 } 709 if (!mBackStack.isEmpty()) { 710 if (b == null) { 711 b = new Bundle(); 712 } 713 int[] backStack = new int[mBackStack.size()]; 714 int index = 0; 715 for (NavDestination destination : mBackStack) { 716 backStack[index++] = destination.getId(); 717 } 718 b.putIntArray(KEY_BACK_STACK_IDS, backStack); 719 } 720 return b; 721 } 722 723 /** 724 * Restores all navigation controller state from a bundle. 725 * 726 * <p>State may be saved to a bundle by calling {@link #saveState()}. 727 * Restoring controller state is the responsibility of a {@link NavHost}.</p> 728 * 729 * @param navState state bundle to restore 730 */ 731 public void restoreState(@Nullable Bundle navState) { 732 if (navState == null) { 733 return; 734 } 735 736 mGraphId = navState.getInt(KEY_GRAPH_ID); 737 mNavigatorStateToRestore = navState.getBundle(KEY_NAVIGATOR_STATE); 738 mBackStackToRestore = navState.getIntArray(KEY_BACK_STACK_IDS); 739 if (mGraphId != 0) { 740 // Set the graph right away, onGraphCreated will handle restoring the 741 // rest of the saved state 742 setGraph(mGraphId); 743 } 744 } 745} 746