1 /* <lambda>null2 * 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 package androidx.navigation 17 18 import androidx.annotation.CallSuper 19 import androidx.annotation.RestrictTo 20 import androidx.savedstate.SavedState 21 22 /** 23 * Navigator defines a mechanism for navigating within an app. 24 * 25 * Each Navigator sets the policy for a specific type of navigation, e.g. [ActivityNavigator] knows 26 * how to launch into [destinations][NavDestination] backed by activities using 27 * [startActivity][Context.startActivity]. 28 * 29 * Navigators should be able to manage their own back stack when navigating between two destinations 30 * that belong to that navigator. The [NavController] manages a back stack of navigators 31 * representing the current navigation stack across all navigators. 32 * 33 * Each Navigator should add the [Navigator.Name annotation][Name] to their class. Any custom 34 * attributes used by the associated [destination][NavDestination] subclass should have a name 35 * corresponding with the name of the Navigator, e.g., [ActivityNavigator] uses `<declare-styleable 36 * name="ActivityNavigator">` 37 * 38 * @param D the subclass of [NavDestination] used with this Navigator which can be used to hold any 39 * special data that will be needed to navigate to that destination. Examples include information 40 * about an intent to navigate to other activities, or a fragment class name to instantiate and 41 * swap to a new fragment. 42 */ 43 public abstract class Navigator<D : NavDestination> { 44 public constructor() { 45 _name = null 46 } 47 48 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 49 public constructor(name: String) { 50 _name = name 51 } 52 53 private val _name: String? 54 55 internal val name: String 56 get() = _name ?: this::class.simpleName!!.removeSuffix("Navigator") 57 58 /** 59 * This annotation should be added to each Navigator subclass in Android to denote the default 60 * name used to register the Navigator with a [NavigatorProvider]. 61 * 62 * On all non-Android platforms, this annotation will do nothing and you should set the name via 63 * the constructor or it will default to the className with any "Navigator" suffix removed. 64 * (i.e. DestinationNavigator will become Destination.) 65 * 66 * @see NavigatorProvider.addNavigator 67 * @see NavigatorProvider.getNavigator 68 */ 69 @Retention(AnnotationRetention.RUNTIME) 70 @Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS) 71 public annotation class Name(val value: String) 72 73 private var _state: NavigatorState? = null 74 75 /** 76 * The state of the Navigator is the communication conduit between the Navigator and the 77 * [NavController] that has called [onAttach]. 78 * 79 * It is the responsibility of the Navigator to call [NavigatorState.push] and 80 * [NavigatorState.pop] to in order to update the [NavigatorState.backStack] at the appropriate 81 * times. 82 * 83 * @throws IllegalStateException if [isAttached] is `false` 84 */ 85 protected val state: NavigatorState 86 get() = 87 checkNotNull(_state) { 88 "You cannot access the Navigator's state until the Navigator is attached" 89 } 90 91 /** 92 * Whether this Navigator is actively being used by a [NavController]. 93 * 94 * This is set to `true` when [onAttach] is called. 95 */ 96 public var isAttached: Boolean = false 97 private set 98 99 /** 100 * Indicator that this Navigator is actively being used by a [NavController]. This is called 101 * when the NavController's state is ready to be restored. 102 */ 103 @CallSuper 104 public open fun onAttach(state: NavigatorState) { 105 _state = state 106 isAttached = true 107 } 108 109 /** 110 * Construct a new NavDestination associated with this Navigator. 111 * 112 * Any initialization of the destination should be done in the destination's constructor as it 113 * is not guaranteed that every destination will be created through this method. 114 * 115 * @return a new NavDestination 116 */ 117 public abstract fun createDestination(): D 118 119 /** 120 * Navigate to a destination. 121 * 122 * Requests navigation to a given destination associated with this navigator in the navigation 123 * graph. This method generally should not be called directly; [NavController] will delegate to 124 * it when appropriate. 125 * 126 * @param entries destination(s) to navigate to 127 * @param navOptions additional options for navigation 128 * @param navigatorExtras extras unique to your Navigator. 129 */ 130 @Suppress("UNCHECKED_CAST") 131 public open fun navigate( 132 entries: List<NavBackStackEntry>, 133 navOptions: NavOptions?, 134 navigatorExtras: Extras? 135 ) { 136 entries 137 .asSequence() 138 .map { backStackEntry -> 139 val destination = backStackEntry.destination as? D ?: return@map null 140 val navigatedToDestination = 141 navigate(destination, backStackEntry.arguments, navOptions, navigatorExtras) 142 when (navigatedToDestination) { 143 null -> null 144 destination -> backStackEntry 145 else -> { 146 state.createBackStackEntry( 147 navigatedToDestination, 148 navigatedToDestination.addInDefaultArgs(backStackEntry.arguments) 149 ) 150 } 151 } 152 } 153 .filterNotNull() 154 .forEach { backStackEntry -> state.push(backStackEntry) } 155 } 156 157 /** 158 * Informational callback indicating that the given [backStackEntry] has been affected by a 159 * [NavOptions.shouldLaunchSingleTop] operation. The entry provided is a new [NavBackStackEntry] 160 * instance with all the previous state of the old entry and possibly new arguments. 161 */ 162 @Suppress("UNCHECKED_CAST") 163 public open fun onLaunchSingleTop(backStackEntry: NavBackStackEntry) { 164 val destination = backStackEntry.destination as? D ?: return 165 navigate(destination, null, navOptions { launchSingleTop = true }, null) 166 state.onLaunchSingleTop(backStackEntry) 167 } 168 169 /** 170 * Navigate to a destination. 171 * 172 * Requests navigation to a given destination associated with this navigator in the navigation 173 * graph. This method generally should not be called directly; [NavController] will delegate to 174 * it when appropriate. 175 * 176 * @param destination destination node to navigate to 177 * @param args arguments to use for navigation 178 * @param navOptions additional options for navigation 179 * @param navigatorExtras extras unique to your Navigator. 180 * @return The NavDestination that should be added to the back stack or null if no change was 181 * made to the back stack (i.e., in cases of single top operations where the destination is 182 * already on top of the back stack). 183 */ 184 // TODO Deprecate this method once all call sites are removed 185 @Suppress("UNUSED_PARAMETER", "RedundantNullableReturnType") 186 public open fun navigate( 187 destination: D, 188 args: SavedState?, 189 navOptions: NavOptions?, 190 navigatorExtras: Extras? 191 ): NavDestination? = destination 192 193 /** 194 * Attempt to pop this navigator's back stack, performing the appropriate navigation. 195 * 196 * All destinations back to [popUpTo] should be popped off the back stack. 197 * 198 * @param popUpTo the entry that should be popped off the [NavigatorState.backStack] along with 199 * all entries above this entry. 200 * @param savedState whether any Navigator specific state associated with [popUpTo] should be 201 * saved to later be restored by a call to [navigate] with [NavOptions.shouldRestoreState]. 202 */ 203 @Suppress("UNUSED_PARAMETER") 204 public open fun popBackStack(popUpTo: NavBackStackEntry, savedState: Boolean) { 205 val backStack = state.backStack.value 206 check(backStack.contains(popUpTo)) { 207 "popBackStack was called with $popUpTo which does not exist in back stack $backStack" 208 } 209 val iterator = backStack.listIterator(backStack.size) 210 var lastPoppedEntry: NavBackStackEntry? = null 211 do { 212 if (!popBackStack()) { 213 // Quit early if popBackStack() returned false 214 break 215 } 216 lastPoppedEntry = iterator.previous() 217 } while (lastPoppedEntry != popUpTo) 218 if (lastPoppedEntry != null) { 219 state.pop(lastPoppedEntry, savedState) 220 } 221 } 222 223 /** 224 * Attempt to pop this navigator's back stack, performing the appropriate navigation. 225 * 226 * Implementations should return `true` if navigation was successful. Implementations should 227 * return `false` if navigation could not be performed, for example if the navigator's back 228 * stack was empty. 229 * 230 * @return `true` if pop was successful 231 */ 232 // TODO Deprecate this method once all call sites are removed 233 public open fun popBackStack(): Boolean = true 234 235 /** 236 * Called to ask for a [SavedState] representing the Navigator's state. This will be restored in 237 * [onRestoreState]. 238 */ 239 public open fun onSaveState(): SavedState? { 240 return null 241 } 242 243 /** 244 * Restore any state previously saved in [onSaveState]. This will be called before any calls to 245 * [navigate] or [popBackStack]. 246 * 247 * Calls to [createDestination] should not be dependent on any state restored here as 248 * [createDestination] can be called before the state is restored. 249 * 250 * @param savedState The state previously saved 251 */ 252 public open fun onRestoreState(savedState: SavedState) {} 253 254 /** 255 * Interface indicating that this class should be passed to its respective [Navigator] to enable 256 * Navigator specific behavior. 257 */ 258 public interface Extras 259 } 260