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