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