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