1 /*
<lambda>null2  * Copyright 2018 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.RestrictTo
19 import androidx.savedstate.SavedState
20 import androidx.savedstate.read
21 
22 /**
23  * NavArgument denotes an argument that is supported by a [NavDestination].
24  *
25  * A NavArgument has a type and optionally a default value, that are used to read/write it in a
26  * SavedState. It can also be nullable if the type supports it.
27  */
28 public class NavArgument
29 internal constructor(
30     type: NavType<Any?>,
31     isNullable: Boolean,
32     defaultValue: Any?,
33     defaultValuePresent: Boolean,
34     unknownDefaultValuePresent: Boolean,
35 ) {
36     /**
37      * The type of this NavArgument.
38      *
39      * @return the NavType object denoting the type that can be help in this argument.
40      */
41     public val type: NavType<Any?>
42 
43     /**
44      * Whether this argument allows passing a `null` value.
45      *
46      * @return true if `null` is allowed, false otherwise
47      */
48     public val isNullable: Boolean
49 
50     /**
51      * Used to distinguish between a default value of `null` and an argument without an explicit
52      * default value.
53      *
54      * @return true if this argument has a default value (even if that value is set to null), false
55      *   otherwise
56      */
57     public val isDefaultValuePresent: Boolean
58 
59     /**
60      * Indicates whether the default value (if present) is unknown (i.e. safe args where default
61      * value is declared in KClass but not stored in [defaultValue]).
62      */
63     internal val isDefaultValueUnknown: Boolean
64 
65     /**
66      * The default value of this argument or `null` if it doesn't have a default value. Use
67      * [isDefaultValuePresent] to distinguish between `null` and absence of a value.
68      *
69      * @return The default value assigned to this argument.
70      */
71     public val defaultValue: Any?
72 
73     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
74     public fun putDefaultValue(name: String, bundle: SavedState) {
75         // even if there is defaultValuePresent, the defaultValue itself could be null as in the
76         // case of safe args where we know there is default value present but we are not able to
77         // read the actual default (serializer limitations), so the defaultValue is set to null.
78         if (isDefaultValuePresent && defaultValue != null) {
79             type.put(bundle, name, defaultValue)
80         }
81     }
82 
83     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
84     @Suppress("DEPRECATION")
85     public fun verify(name: String, bundle: SavedState): Boolean {
86         if (!isNullable && bundle.read { contains(name) && isNull(name) }) {
87             return false
88         }
89         try {
90             type[bundle, name]
91         } catch (e: IllegalStateException) {
92             return false
93         }
94         return true
95     }
96 
97     override fun toString(): String {
98         val sb = StringBuilder()
99         sb.append(this::class.simpleName)
100         sb.append(" Type: $type")
101         sb.append(" Nullable: $isNullable")
102         if (isDefaultValuePresent) {
103             sb.append(" DefaultValue: $defaultValue")
104         }
105         return sb.toString()
106     }
107 
108     public override fun equals(other: Any?): Boolean {
109         if (this === other) return true
110         if (other == null || this::class != other::class) return false
111         val that = other as NavArgument
112         if (isNullable != that.isNullable) return false
113         if (isDefaultValuePresent != that.isDefaultValuePresent) return false
114         if (type != that.type) return false
115         return if (defaultValue != null) {
116             defaultValue == that.defaultValue
117         } else {
118             that.defaultValue == null
119         }
120     }
121 
122     public override fun hashCode(): Int {
123         var result = type.hashCode()
124         result = 31 * result + if (isNullable) 1 else 0
125         result = 31 * result + if (isDefaultValuePresent) 1 else 0
126         result = 31 * result + (defaultValue?.hashCode() ?: 0)
127         return result
128     }
129 
130     /** A builder for constructing [NavArgument] instances. */
131     @Suppress("UNCHECKED_CAST")
132     public class Builder {
133         private var type: NavType<Any?>? = null
134         private var isNullable = false
135         private var defaultValue: Any? = null
136         private var defaultValuePresent = false
137         private var unknownDefaultValuePresent = false
138 
139         /**
140          * Set the type of the argument.
141          *
142          * @param type Type of the argument.
143          * @return This builder.
144          */
145         public fun <T> setType(type: NavType<T>): Builder {
146             this.type = type as NavType<Any?>
147             return this
148         }
149 
150         /**
151          * Specify if the argument is nullable. The NavType you set for this argument must allow
152          * nullable values.
153          *
154          * @param isNullable Argument will be nullable if true.
155          * @return This builder.
156          * @see NavType.isNullableAllowed
157          */
158         public fun setIsNullable(isNullable: Boolean): Builder {
159             this.isNullable = isNullable
160             return this
161         }
162 
163         /**
164          * Specify the default value for an argument. Calling this at least once will cause the
165          * argument to have a default value, even if it is set to null.
166          *
167          * @param defaultValue Default value for this argument. Must match NavType if it is
168          *   specified.
169          * @return This builder.
170          */
171         public fun setDefaultValue(defaultValue: Any?): Builder {
172             this.defaultValue = defaultValue
173             defaultValuePresent = true
174             return this
175         }
176 
177         /**
178          * Set whether there is an unknown default value present.
179          *
180          * Use with caution!! In general you should let [setDefaultValue] to automatically set this
181          * state. This state should be set to true only if all these conditions are met:
182          * 1. There is default value present
183          * 2. You do not have access to actual default value (thus you can't use [defaultValue])
184          * 3. You know the default value will never ever be null if [isNullable] is true.
185          */
186         internal fun setUnknownDefaultValuePresent(unknownDefaultValuePresent: Boolean): Builder {
187             this.unknownDefaultValuePresent = unknownDefaultValuePresent
188             return this
189         }
190 
191         /**
192          * Build the NavArgument specified by this builder. If the type is not set, the builder will
193          * infer the type from the default argument value. If there is no default value, the type
194          * will be unspecified.
195          *
196          * @return the newly constructed NavArgument.
197          */
198         public fun build(): NavArgument {
199             val finalType = type ?: NavType.inferFromValueType(defaultValue) as NavType<Any?>
200             return NavArgument(
201                 finalType,
202                 isNullable,
203                 defaultValue,
204                 defaultValuePresent,
205                 unknownDefaultValuePresent
206             )
207         }
208     }
209 
210     init {
211         require(!(!type.isNullableAllowed && isNullable)) {
212             "${type.name} does not allow nullable values"
213         }
214         require(!(!isNullable && defaultValuePresent && defaultValue == null)) {
215             "Argument with type ${type.name} has null value but is not nullable."
216         }
217         this.type = type
218         this.isNullable = isNullable
219         this.defaultValue = defaultValue
220         isDefaultValuePresent = defaultValuePresent || unknownDefaultValuePresent
221         isDefaultValueUnknown = unknownDefaultValuePresent
222     }
223 }
224 
225 /**
226  * Returns a list of NavArgument keys where required NavArguments with that key returns false for
227  * the predicate `isArgumentMissing`.
228  *
229  * @param [isArgumentMissing] predicate that returns true if the key of a required NavArgument is
230  *   missing from a SavedState that is expected to contain it.
231  */
missingRequiredArgumentsnull232 internal fun Map<String, NavArgument?>.missingRequiredArguments(
233     isArgumentMissing: (key: String) -> Boolean
234 ): List<String> {
235     val requiredArgumentKeys = filterValues { !it?.isNullable!! && !it.isDefaultValuePresent }.keys
236 
237     return requiredArgumentKeys.filter { key -> isArgumentMissing(key) }
238 }
239