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.savedstate.read
19 import androidx.savedstate.savedState
20 import kotlinx.coroutines.flow.StateFlow
21 
22 /**
23  * A Navigator built specifically for [NavGraph] elements. Handles navigating to the correct
24  * destination when the NavGraph is the target of navigation actions.
25  *
26  * Construct a Navigator capable of routing incoming navigation requests to the proper destination
27  * within a [NavGraph].
28  *
29  * @param navigatorProvider NavigatorProvider used to retrieve the correct [Navigator] to navigate
30  *   to the start destination
31  */
32 @Navigator.Name("navigation")
33 public open class NavGraphNavigator(private val navigatorProvider: NavigatorProvider) :
34     Navigator<NavGraph>(NAME) {
35 
36     /** Gets the backstack of [NavBackStackEntry] associated with this Navigator */
37     public val backStack: StateFlow<List<NavBackStackEntry>>
38         get() = state.backStack
39 
40     /**
41      * Creates a new [NavGraph] associated with this navigator.
42      *
43      * @return The created [NavGraph].
44      */
45     override fun createDestination(): NavGraph {
46         return NavGraph(this)
47     }
48 
49     /**
50      * @throws IllegalArgumentException if given destination is not a child of the current navgraph
51      */
52     override fun navigate(
53         entries: List<NavBackStackEntry>,
54         navOptions: NavOptions?,
55         navigatorExtras: Extras?
56     ) {
57         for (entry in entries) {
58             navigate(entry, navOptions, navigatorExtras)
59         }
60     }
61 
62     private fun navigate(
63         entry: NavBackStackEntry,
64         navOptions: NavOptions?,
65         navigatorExtras: Extras?
66     ) {
67         val destination = entry.destination as NavGraph
68         // contains restored args or args passed explicitly as startDestinationArgs
69         var args = entry.arguments
70         val startId = destination.startDestinationId
71         val startRoute = destination.startDestinationRoute
72         check(startId != 0 || startRoute != null) {
73             ("no start destination defined via app:startDestination for ${destination.displayName}")
74         }
75         val startDestination =
76             if (startRoute != null) {
77                 destination.findNode(startRoute, false)
78             } else {
79                 destination.nodes[startId]
80             }
81         requireNotNull(startDestination) {
82             val dest = destination.startDestDisplayName
83             throw IllegalArgumentException(
84                 "navigation destination $dest is not a direct child of this NavGraph"
85             )
86         }
87         if (startRoute != null) {
88             // If startRoute contains only placeholders, we fallback to default arg values.
89             // This is to maintain existing behavior of using default value for startDestination
90             // while also adding support for args declared in startRoute.
91             if (startRoute != startDestination.route) {
92                 val matchingArgs = startDestination.matchRoute(startRoute)?.matchingArgs
93                 if (matchingArgs != null && !matchingArgs.read { isEmpty() }) {
94                     args = savedState {
95                         // we need to add args from startRoute,
96                         // but it should not override existing args
97                         putAll(matchingArgs)
98                         args?.let { putAll(it) }
99                     }
100                 }
101             }
102             // by this point, the bundle should contain all arguments that don't have
103             // default values (regardless of whether the actual default value is known or not).
104             if (startDestination.arguments.isNotEmpty()) {
105                 val missingRequiredArgs =
106                     startDestination.arguments.missingRequiredArguments { key ->
107                         if (args == null) true else !args.read { contains(key) }
108                     }
109                 require(missingRequiredArgs.isEmpty()) {
110                     "Cannot navigate to startDestination $startDestination. " +
111                         "Missing required arguments [$missingRequiredArgs]"
112                 }
113             }
114         }
115 
116         val navigator =
117             navigatorProvider.getNavigator<Navigator<NavDestination>>(
118                 startDestination.navigatorName
119             )
120         val startDestinationEntry =
121             state.createBackStackEntry(
122                 startDestination,
123                 // could contain default args, restored args, args passed during setGraph,
124                 // and args from route
125                 startDestination.addInDefaultArgs(args)
126             )
127         navigator.navigate(listOf(startDestinationEntry), navOptions, navigatorExtras)
128     }
129 
130     internal companion object {
131         internal const val NAME = "navigation"
132     }
133 }
134