1 /*
2 * 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.navigation.NavType.Companion.BoolArrayType
20 import androidx.navigation.NavType.Companion.BoolListType
21 import androidx.navigation.NavType.Companion.BoolType
22 import androidx.navigation.NavType.Companion.FloatArrayType
23 import androidx.navigation.NavType.Companion.FloatListType
24 import androidx.navigation.NavType.Companion.FloatType
25 import androidx.navigation.NavType.Companion.IntArrayType
26 import androidx.navigation.NavType.Companion.IntListType
27 import androidx.navigation.NavType.Companion.IntType
28 import androidx.navigation.NavType.Companion.LongArrayType
29 import androidx.navigation.NavType.Companion.LongListType
30 import androidx.navigation.NavType.Companion.LongType
31 import androidx.navigation.NavType.Companion.StringArrayType
32 import androidx.navigation.NavType.Companion.StringListType
33 import androidx.navigation.NavType.Companion.StringType
34 import androidx.savedstate.SavedState
35 import androidx.savedstate.read
36 import androidx.savedstate.write
37 import kotlin.jvm.JvmStatic
38
39 /**
40 * NavType denotes the type that can be used in a [NavArgument].
41 *
42 * There are built-in NavTypes for primitive types, such as int, long, boolean, float, and strings,
43 * parcelable, and serializable classes (including Enums), as well as arrays of each supported type.
44 *
45 * You should only use one of the static NavType instances and subclasses defined in this class.
46 *
47 * @param T the type of the data that is supported by this NavType
48 * @param isNullableAllowed whether an argument with this type can hold a null value.
49 */
50 public expect abstract class NavType<T>(isNullableAllowed: Boolean) {
51 /**
52 * Check if an argument with this type can hold a null value.
53 *
54 * @return Returns true if this type allows null values, false otherwise.
55 */
56 public open val isNullableAllowed: Boolean
57
58 /**
59 * Put a value of this type in the `savedState`
60 *
61 * @param bundle savedState to put value in
62 * @param key savedState key
63 * @param value value of this type
64 */
putnull65 public abstract fun put(bundle: SavedState, key: String, value: T)
66
67 /**
68 * Get a value of this type from the `savedState`
69 *
70 * @param bundle savedState to get value from
71 * @param key savedState key
72 * @return value of this type
73 */
74 public abstract operator fun get(bundle: SavedState, key: String): T?
75
76 /**
77 * Parse a value of this type from a String.
78 *
79 * @param value string representation of a value of this type
80 * @return parsed value of the type represented by this NavType
81 * @throws IllegalArgumentException if value cannot be parsed into this type
82 */
83 public abstract fun parseValue(value: String): T
84
85 /**
86 * Parse a value of this type from a String and then combine that parsed value with the given
87 * previousValue of the same type to provide a new value that contains both the new and previous
88 * value.
89 *
90 * By default, the given value will replace the previousValue.
91 *
92 * @param value string representation of a value of this type
93 * @param previousValue previously parsed value of this type
94 * @return combined parsed value of the type represented by this NavType
95 * @throws IllegalArgumentException if value cannot be parsed into this type
96 */
97 public open fun parseValue(value: String, previousValue: T): T
98
99 /**
100 * Parse a value of this type from a String and put it in a `savedState`
101 *
102 * @param bundle savedState to put value in
103 * @param key savedState key under which to put the value
104 * @param value string representation of a value of this type
105 * @return parsed value of the type represented by this NavType
106 */
107 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
108 public fun parseAndPut(bundle: SavedState, key: String, value: String): T
109
110 /**
111 * Parse a value of this type from a String, combine that parsed value with the given
112 * previousValue, and then put that combined parsed value in a `savedState`.
113 *
114 * @param bundle savedState to put value in
115 * @param key savedState key under which to put the value
116 * @param value string representation of a value of this type
117 * @param previousValue previously parsed value of this type
118 * @return combined parsed value of the type represented by this NavType
119 */
120 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
121 public fun parseAndPut(bundle: SavedState, key: String, value: String?, previousValue: T): T
122
123 /**
124 * Serialize a value of this NavType into a String.
125 *
126 * By default it returns value of [kotlin.toString] or null if value passed in is null.
127 *
128 * This method can be override for custom serialization implementation on types such custom
129 * NavType classes.
130 *
131 * Note: Final output should be encoded with [NavUriUtils.encode]
132 *
133 * @param value a value representing this NavType to be serialized into a String
134 * @return encoded and serialized String value of [value]
135 */
136 public open fun serializeAsValue(value: T): String
137
138 /**
139 * The name of this type.
140 *
141 * This is the same value that is used in Navigation XML `argType` attribute.
142 *
143 * @return name of this type
144 */
145 public open val name: String
146
147 /**
148 * Compares two values of type [T] and returns true if values are equal.
149 *
150 * @param value the first value for comparison
151 * @param other the second value for comparison
152 */
153 public open fun valueEquals(value: T, other: T): Boolean
154
155 public companion object {
156 /**
157 * Parse an argType string into a NavType.
158 *
159 * @param type argType string, usually parsed from the Navigation XML file
160 * @param packageName package name of the R file, used for parsing relative class names
161 * starting with a dot.
162 * @return a NavType representing the type indicated by the argType string. Defaults to
163 * StringType for null.
164 * @throws IllegalArgumentException if there is no valid argType
165 * @throws RuntimeException if the type class name cannot be found
166 */
167 @Suppress("NON_FINAL_MEMBER_IN_OBJECT", "UNCHECKED_CAST") // this needs to be open to
168 // maintain api compatibility and type cast are unchecked
169 @JvmStatic
170 public open fun fromArgType(type: String?, packageName: String?): NavType<*>
171
172 @Suppress("UNCHECKED_CAST") // needed for cast to NavType<Any>
173 @JvmStatic
174 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
175 public fun inferFromValue(value: String): NavType<Any>
176
177 /**
178 * @param value nothing
179 * @throws IllegalArgumentException not real
180 */
181 @Suppress("UNCHECKED_CAST") // needed for cast to NavType<Any>
182 @JvmStatic
183 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
184 public fun inferFromValueType(value: Any?): NavType<Any>
185
186 /**
187 * NavType for storing integer values, corresponding with the "integer" type in a Navigation
188 * XML file.
189 *
190 * Null values are not supported.
191 */
192 public val IntType: NavType<Int>
193
194 /**
195 * NavType for storing integer arrays, corresponding with the "integer[]" type in a
196 * Navigation XML file.
197 *
198 * Null values are supported. Default values in Navigation XML files are not supported.
199 */
200 public val IntArrayType: NavType<IntArray?>
201
202 /**
203 * NavType for storing list of Ints.
204 *
205 * Null values are supported. List NavTypes in Navigation XML files are not supported.
206 */
207 public val IntListType: NavType<List<Int>?>
208
209 /**
210 * NavType for storing long values, corresponding with the "long" type in a Navigation XML
211 * file.
212 *
213 * Null values are not supported. Default values for this type in Navigation XML files must
214 * always end with an 'L' suffix, e.g. `app:defaultValue="123L"`.
215 */
216 public val LongType: NavType<Long>
217
218 /**
219 * NavType for storing long arrays, corresponding with the "long[]" type in a Navigation XML
220 * file.
221 *
222 * Null values are supported. Default values in Navigation XML files are not supported.
223 */
224 public val LongArrayType: NavType<LongArray?>
225
226 /**
227 * NavType for storing list of Longs.
228 *
229 * Null values are supported. List NavTypes in Navigation XML files are not supported.
230 */
231 public val LongListType: NavType<List<Long>?>
232
233 /**
234 * NavType for storing float values, corresponding with the "float" type in a Navigation XML
235 * file.
236 *
237 * Null values are not supported.
238 */
239 public val FloatType: NavType<Float>
240
241 /**
242 * NavType for storing float arrays, corresponding with the "float[]" type in a Navigation
243 * XML file.
244 *
245 * Null values are supported. Default values in Navigation XML files are not supported.
246 */
247 public val FloatArrayType: NavType<FloatArray?>
248
249 /**
250 * NavType for storing list of Floats.
251 *
252 * Null values are supported. List NavTypes in Navigation XML files are not supported.
253 */
254 public val FloatListType: NavType<List<Float>?>
255
256 /**
257 * NavType for storing boolean values, corresponding with the "boolean" type in a Navigation
258 * XML file.
259 *
260 * Null values are not supported.
261 */
262 public val BoolType: NavType<Boolean>
263
264 /**
265 * NavType for storing boolean arrays, corresponding with the "boolean[]" type in a
266 * Navigation XML file.
267 *
268 * Null values are supported. Default values in Navigation XML files are not supported.
269 */
270 public val BoolArrayType: NavType<BooleanArray?>
271
272 /**
273 * NavType for storing list of Booleans.
274 *
275 * Null values are supported. List NavTypes in Navigation XML files are not supported.
276 */
277 public val BoolListType: NavType<List<Boolean>?>
278
279 /**
280 * NavType for storing String values, corresponding with the "string" type in a Navigation
281 * XML file.
282 *
283 * Null values are supported.
284 */
285 public val StringType: NavType<String?>
286
287 /**
288 * NavType for storing String arrays, corresponding with the "string[]" type in a Navigation
289 * XML file.
290 *
291 * Null values are supported. Default values in Navigation XML files are not supported.
292 */
293 public val StringArrayType: NavType<Array<String>?>
294
295 /**
296 * NavType for storing list of Strings.
297 *
298 * Null values are supported. List NavTypes in Navigation XML files are not supported.
299 */
300 public val StringListType: NavType<List<String>?>
301 }
302 }
303
navTypeParseAndPutnull304 internal fun <T> NavType<T>.navTypeParseAndPut(bundle: SavedState, key: String, value: String): T {
305 val parsedValue = parseValue(value)
306 put(bundle, key, parsedValue)
307 return parsedValue
308 }
309
navTypeParseAndPutnull310 internal fun <T> NavType<T>.navTypeParseAndPut(
311 bundle: SavedState,
312 key: String,
313 value: String?,
314 previousValue: T
315 ): T {
316 if (!bundle.read { contains(key) }) {
317 throw IllegalArgumentException("There is no previous value in this savedState.")
318 }
319 if (value != null) {
320 val parsedCombinedValue = parseValue(value, previousValue)
321 put(bundle, key, parsedCombinedValue)
322 return parsedCombinedValue
323 }
324 return previousValue
325 }
326
navTypeFromArgTypenull327 internal fun navTypeFromArgType(type: String?): NavType<*>? {
328 when {
329 IntType.name == type -> return IntType
330 IntArrayType.name == type -> return IntArrayType
331 IntListType.name == type -> return IntListType
332 LongType.name == type -> return LongType
333 LongArrayType.name == type -> return LongArrayType
334 LongListType.name == type -> return LongListType
335 BoolType.name == type -> return BoolType
336 BoolArrayType.name == type -> return BoolArrayType
337 BoolListType.name == type -> return BoolListType
338 StringType.name == type -> return StringType
339 StringArrayType.name == type -> return StringArrayType
340 StringListType.name == type -> return StringListType
341 FloatType.name == type -> return FloatType
342 FloatArrayType.name == type -> return FloatArrayType
343 FloatListType.name == type -> return FloatListType
344 }
345 return null
346 }
347
348 @Suppress("UNCHECKED_CAST") // needed for cast to NavType<Any>
navTypeInferFromValuenull349 internal fun navTypeInferFromValue(value: String): NavType<Any> {
350 // because we allow Long literals without the L suffix at runtime,
351 // the order of IntType and LongType parsing has to be reversed compared to Safe Args
352 try {
353 IntType.parseValue(value)
354 return IntType as NavType<Any>
355 } catch (_: IllegalArgumentException) {
356 // ignored, proceed to check next type
357 }
358 try {
359 LongType.parseValue(value)
360 return LongType as NavType<Any>
361 } catch (_: IllegalArgumentException) {
362 // ignored, proceed to check next type
363 }
364 try {
365 FloatType.parseValue(value)
366 return FloatType as NavType<Any>
367 } catch (_: IllegalArgumentException) {
368 // ignored, proceed to check next type
369 }
370 try {
371 BoolType.parseValue(value)
372 return BoolType as NavType<Any>
373 } catch (_: IllegalArgumentException) {
374 // ignored, proceed to check next type
375 }
376 return StringType as NavType<Any>
377 }
378
379 @Suppress("UNCHECKED_CAST") // needed for cast to NavType<Any>
navTypeInferFromValueTypenull380 internal fun navTypeInferFromValueType(value: Any?): NavType<Any>? {
381 return when (value) {
382 is Int -> IntType as NavType<Any>
383 is IntArray -> IntArrayType as NavType<Any>
384 is Long -> LongType as NavType<Any>
385 is LongArray -> LongArrayType as NavType<Any>
386 is Float -> FloatType as NavType<Any>
387 is FloatArray -> FloatArrayType as NavType<Any>
388 is Boolean -> BoolType as NavType<Any>
389 is BooleanArray -> BoolArrayType as NavType<Any>
390 is String,
391 null -> StringType as NavType<Any>
392 else -> null
393 }
394 }
395
396 internal class IntNavType : NavType<Int>(false) {
397 override val name: String
398 get() = "integer"
399
putnull400 override fun put(bundle: SavedState, key: String, value: Int) {
401 bundle.write { putInt(key, value) }
402 }
403
404 @Suppress("DEPRECATION")
getnull405 override fun get(bundle: SavedState, key: String): Int {
406 return bundle.read { getInt(key) }
407 }
408
parseValuenull409 override fun parseValue(value: String): Int {
410 return if (value.startsWith("0x")) {
411 value.substring(2).toInt(16)
412 } else {
413 value.toInt()
414 }
415 }
416 }
417
418 internal class IntArrayNavType : CollectionNavType<IntArray?>(true) {
419 override val name: String
420 get() = "integer[]"
421
putnull422 override fun put(bundle: SavedState, key: String, value: IntArray?) {
423 bundle.write { if (value != null) putIntArray(key, value) else putNull(key) }
424 }
425
426 @Suppress("DEPRECATION")
getnull427 override fun get(bundle: SavedState, key: String): IntArray? {
428 return bundle.read { if (!contains(key) || isNull(key)) null else getIntArray(key) }
429 }
430
parseValuenull431 override fun parseValue(value: String): IntArray {
432 return intArrayOf(IntType.parseValue(value))
433 }
434
parseValuenull435 override fun parseValue(value: String, previousValue: IntArray?): IntArray {
436 return previousValue?.plus(parseValue(value)) ?: parseValue(value)
437 }
438
valueEqualsnull439 override fun valueEquals(value: IntArray?, other: IntArray?): Boolean {
440 val valueArray = value?.toTypedArray()
441 val otherArray = other?.toTypedArray()
442 return valueArray.contentDeepEquals(otherArray)
443 }
444
serializeAsValuesnull445 override fun serializeAsValues(value: IntArray?): List<String> =
446 value?.toList()?.map { it.toString() } ?: emptyList()
447
emptyCollectionnull448 override fun emptyCollection(): IntArray = intArrayOf()
449 }
450
451 internal class IntListNavType : CollectionNavType<List<Int>?>(true) {
452 override val name: String
453 get() = "List<Int>"
454
455 override fun put(bundle: SavedState, key: String, value: List<Int>?) {
456 if (value != null) {
457 bundle.write { putIntArray(key, value.toIntArray()) }
458 }
459 }
460
461 @Suppress("DEPRECATION")
462 override fun get(bundle: SavedState, key: String): List<Int>? {
463 return bundle.read {
464 if (!contains(key) || isNull(key)) null else getIntArray(key).toList()
465 }
466 }
467
468 override fun parseValue(value: String): List<Int> {
469 return listOf(IntType.parseValue(value))
470 }
471
472 override fun parseValue(value: String, previousValue: List<Int>?): List<Int>? {
473 return previousValue?.plus(parseValue(value)) ?: parseValue(value)
474 }
475
476 override fun valueEquals(value: List<Int>?, other: List<Int>?): Boolean {
477 val valueArray = value?.toTypedArray()
478 val otherArray = other?.toTypedArray()
479 return valueArray.contentDeepEquals(otherArray)
480 }
481
482 override fun serializeAsValues(value: List<Int>?): List<String> =
483 value?.map { it.toString() } ?: emptyList()
484
485 override fun emptyCollection(): List<Int> = emptyList()
486 }
487
488 internal class LongNavType : NavType<Long>(false) {
489 override val name: String
490 get() = "long"
491
putnull492 override fun put(bundle: SavedState, key: String, value: Long) {
493 bundle.write { putLong(key, value) }
494 }
495
496 @Suppress("DEPRECATION")
getnull497 override fun get(bundle: SavedState, key: String): Long {
498 return bundle.read { getLong(key) }
499 }
500
parseValuenull501 override fun parseValue(value: String): Long {
502 // At runtime the L suffix is optional, contrary to the Safe Args plugin.
503 // This is in order to be able to parse long numbers passed as deep link URL
504 // parameters
505 var localValue = value
506 if (value.endsWith("L")) {
507 localValue = localValue.substring(0, value.length - 1)
508 }
509 return if (value.startsWith("0x")) {
510 localValue.substring(2).toLong(16)
511 } else {
512 localValue.toLong()
513 }
514 }
515 }
516
517 internal class LongArrayNavType : CollectionNavType<LongArray?>(true) {
518 override val name: String
519 get() = "long[]"
520
putnull521 override fun put(bundle: SavedState, key: String, value: LongArray?) {
522 bundle.write { if (value != null) putLongArray(key, value) else putNull(key) }
523 }
524
525 @Suppress("DEPRECATION")
getnull526 override fun get(bundle: SavedState, key: String): LongArray? {
527 return bundle.read { if (!contains(key) || isNull(key)) null else getLongArray(key) }
528 }
529
parseValuenull530 override fun parseValue(value: String): LongArray {
531 return longArrayOf(LongType.parseValue(value))
532 }
533
parseValuenull534 override fun parseValue(value: String, previousValue: LongArray?): LongArray? {
535 return previousValue?.plus(parseValue(value)) ?: parseValue(value)
536 }
537
valueEqualsnull538 override fun valueEquals(value: LongArray?, other: LongArray?): Boolean {
539 val valueArray = value?.toTypedArray()
540 val otherArray = other?.toTypedArray()
541 return valueArray.contentDeepEquals(otherArray)
542 }
543
serializeAsValuesnull544 override fun serializeAsValues(value: LongArray?): List<String> =
545 value?.toList()?.map { it.toString() } ?: emptyList()
546
emptyCollectionnull547 override fun emptyCollection(): LongArray = longArrayOf()
548 }
549
550 internal class LongListNavType : CollectionNavType<List<Long>?>(true) {
551 override val name: String
552 get() = "List<Long>"
553
554 override fun put(bundle: SavedState, key: String, value: List<Long>?) {
555 bundle.write { if (value != null) putLongArray(key, value.toLongArray()) else putNull(key) }
556 }
557
558 @Suppress("DEPRECATION")
559 override fun get(bundle: SavedState, key: String): List<Long>? {
560 return bundle.read {
561 if (!contains(key) || isNull(key)) null else getLongArray(key).toList()
562 }
563 }
564
565 override fun parseValue(value: String): List<Long> {
566 return listOf(LongType.parseValue(value))
567 }
568
569 override fun parseValue(value: String, previousValue: List<Long>?): List<Long>? {
570 return previousValue?.plus(parseValue(value)) ?: parseValue(value)
571 }
572
573 override fun valueEquals(value: List<Long>?, other: List<Long>?): Boolean {
574 val valueArray = value?.toTypedArray()
575 val otherArray = other?.toTypedArray()
576 return valueArray.contentDeepEquals(otherArray)
577 }
578
579 override fun serializeAsValues(value: List<Long>?): List<String> =
580 value?.map { it.toString() } ?: emptyList()
581
582 override fun emptyCollection(): List<Long> = emptyList()
583 }
584
585 internal class FloatNavType : NavType<Float>(false) {
586 override val name: String
587 get() = "float"
588
putnull589 override fun put(bundle: SavedState, key: String, value: Float) {
590 bundle.write { putFloat(key, value) }
591 }
592
593 @Suppress("DEPRECATION")
getnull594 override fun get(bundle: SavedState, key: String): Float {
595 return bundle.read { getFloat(key) }
596 }
597
parseValuenull598 override fun parseValue(value: String): Float {
599 return value.toFloat()
600 }
601 }
602
603 internal class FloatArrayNavType : CollectionNavType<FloatArray?>(true) {
604 override val name: String
605 get() = "float[]"
606
putnull607 override fun put(bundle: SavedState, key: String, value: FloatArray?) {
608 bundle.write { if (value != null) putFloatArray(key, value) else putNull(key) }
609 }
610
611 @Suppress("DEPRECATION")
getnull612 override fun get(bundle: SavedState, key: String): FloatArray? {
613 return bundle.read { if (!contains(key) || isNull(key)) null else getFloatArray(key) }
614 }
615
parseValuenull616 override fun parseValue(value: String): FloatArray {
617 return floatArrayOf(FloatType.parseValue(value))
618 }
619
parseValuenull620 override fun parseValue(value: String, previousValue: FloatArray?): FloatArray? {
621 return previousValue?.plus(parseValue(value)) ?: parseValue(value)
622 }
623
valueEqualsnull624 override fun valueEquals(value: FloatArray?, other: FloatArray?): Boolean {
625 val valueArray = value?.toTypedArray()
626 val otherArray = other?.toTypedArray()
627 return valueArray.contentDeepEquals(otherArray)
628 }
629
serializeAsValuesnull630 override fun serializeAsValues(value: FloatArray?): List<String> =
631 value?.toList()?.map { it.toString() } ?: emptyList()
632
emptyCollectionnull633 override fun emptyCollection(): FloatArray = floatArrayOf()
634 }
635
636 internal class FloatListNavType : CollectionNavType<List<Float>?>(true) {
637 override val name: String
638 get() = "List<Float>"
639
640 override fun put(bundle: SavedState, key: String, value: List<Float>?) {
641 bundle.write {
642 if (value != null) putFloatArray(key, value.toFloatArray()) else putNull(key)
643 }
644 }
645
646 @Suppress("DEPRECATION")
647 override fun get(bundle: SavedState, key: String): List<Float>? {
648 return bundle.read {
649 if (!contains(key) || isNull(key)) null else getFloatArray(key).toList()
650 }
651 }
652
653 override fun parseValue(value: String): List<Float> {
654 return listOf(FloatType.parseValue(value))
655 }
656
657 override fun parseValue(value: String, previousValue: List<Float>?): List<Float>? {
658 return previousValue?.plus(parseValue(value)) ?: parseValue(value)
659 }
660
661 override fun valueEquals(value: List<Float>?, other: List<Float>?): Boolean {
662 val valueArray = value?.toTypedArray()
663 val otherArray = other?.toTypedArray()
664 return valueArray.contentDeepEquals(otherArray)
665 }
666
667 override fun serializeAsValues(value: List<Float>?): List<String> =
668 value?.map { it.toString() } ?: emptyList()
669
670 override fun emptyCollection(): List<Float> = emptyList()
671 }
672
673 internal class BoolNavType : NavType<Boolean>(false) {
674 override val name: String
675 get() = "boolean"
676
putnull677 override fun put(bundle: SavedState, key: String, value: Boolean) {
678 bundle.write { putBoolean(key, value) }
679 }
680
681 @Suppress("DEPRECATION")
getnull682 override fun get(bundle: SavedState, key: String): Boolean? {
683 return bundle.read { if (!contains(key) || isNull(key)) null else getBoolean(key) }
684 }
685
parseValuenull686 override fun parseValue(value: String): Boolean {
687 return when (value) {
688 "true" -> true
689 "false" -> false
690 else -> {
691 throw IllegalArgumentException(
692 "A boolean NavType only accepts \"true\" or \"false\" values."
693 )
694 }
695 }
696 }
697 }
698
699 internal class BoolArrayNavType : CollectionNavType<BooleanArray?>(true) {
700 override val name: String
701 get() = "boolean[]"
702
putnull703 override fun put(bundle: SavedState, key: String, value: BooleanArray?) {
704 bundle.write { if (value != null) putBooleanArray(key, value) else putNull(key) }
705 }
706
707 @Suppress("DEPRECATION")
getnull708 override fun get(bundle: SavedState, key: String): BooleanArray? {
709 return bundle.read { if (!contains(key) || isNull(key)) null else getBooleanArray(key) }
710 }
711
parseValuenull712 override fun parseValue(value: String): BooleanArray {
713 return booleanArrayOf(BoolType.parseValue(value))
714 }
715
parseValuenull716 override fun parseValue(value: String, previousValue: BooleanArray?): BooleanArray? {
717 return previousValue?.plus(parseValue(value)) ?: parseValue(value)
718 }
719
valueEqualsnull720 override fun valueEquals(value: BooleanArray?, other: BooleanArray?): Boolean {
721 val valueArray = value?.toTypedArray()
722 val otherArray = other?.toTypedArray()
723 return valueArray.contentDeepEquals(otherArray)
724 }
725
serializeAsValuesnull726 override fun serializeAsValues(value: BooleanArray?): List<String> =
727 value?.toList()?.map { it.toString() } ?: emptyList()
728
emptyCollectionnull729 override fun emptyCollection(): BooleanArray = booleanArrayOf()
730 }
731
732 internal class BoolListNavType : CollectionNavType<List<Boolean>?>(true) {
733 override val name: String
734 get() = "List<Boolean>"
735
736 override fun put(bundle: SavedState, key: String, value: List<Boolean>?) {
737 bundle.write {
738 if (value != null) putBooleanArray(key, value.toBooleanArray()) else putNull(key)
739 }
740 }
741
742 @Suppress("DEPRECATION")
743 override fun get(bundle: SavedState, key: String): List<Boolean>? {
744 return bundle.read {
745 if (!contains(key) || isNull(key)) null else getBooleanArray(key).toList()
746 }
747 }
748
749 override fun parseValue(value: String): List<Boolean> {
750 return listOf(BoolType.parseValue(value))
751 }
752
753 override fun parseValue(value: String, previousValue: List<Boolean>?): List<Boolean>? {
754 return previousValue?.plus(parseValue(value)) ?: parseValue(value)
755 }
756
757 override fun valueEquals(value: List<Boolean>?, other: List<Boolean>?): Boolean {
758 val valueArray = value?.toTypedArray()
759 val otherArray = other?.toTypedArray()
760 return valueArray.contentDeepEquals(otherArray)
761 }
762
763 override fun serializeAsValues(value: List<Boolean>?): List<String> =
764 value?.map { it.toString() } ?: emptyList()
765
766 override fun emptyCollection(): List<Boolean> = emptyList()
767 }
768
769 internal class StringNavType : NavType<String?>(true) {
770 override val name: String
771 get() = "string"
772
putnull773 override fun put(bundle: SavedState, key: String, value: String?) {
774 bundle.write {
775 if (value != null) {
776 putString(key, value)
777 } else putNull(key)
778 }
779 }
780
781 @Suppress("DEPRECATION")
getnull782 override fun get(bundle: SavedState, key: String): String? {
783 return bundle.read { if (!contains(key) || isNull(key)) null else getString(key) }
784 }
785
786 /**
787 * Returns input value by default.
788 *
789 * If input value is "null", returns null as the reversion of Kotlin standard library
790 * serializing null receivers of [kotlin.toString] into "null".
791 */
parseValuenull792 override fun parseValue(value: String): String? {
793 return if (value == "null") null else value
794 }
795
796 /**
797 * Returns default value of Uri.encode(value).
798 *
799 * If input value is null, returns "null" in compliance with Kotlin standard library parsing
800 * null receivers of [kotlin.toString] into "null".
801 */
serializeAsValuenull802 override fun serializeAsValue(value: String?): String {
803 return value?.let { NavUriUtils.encode(value) } ?: "null"
804 }
805 }
806
807 internal class StringArrayNavType : CollectionNavType<Array<String>?>(true) {
808 override val name: String
809 get() = "string[]"
810
putnull811 override fun put(bundle: SavedState, key: String, value: Array<String>?) {
812 bundle.write { if (value != null) putStringArray(key, value) else putNull(key) }
813 }
814
815 @Suppress("UNCHECKED_CAST", "DEPRECATION")
getnull816 override fun get(bundle: SavedState, key: String): Array<String>? {
817 return bundle.read { if (!contains(key) || isNull(key)) null else getStringArray(key) }
818 }
819
parseValuenull820 override fun parseValue(value: String): Array<String> {
821 return arrayOf(value)
822 }
823
parseValuenull824 override fun parseValue(value: String, previousValue: Array<String>?): Array<String>? {
825 return previousValue?.plus(parseValue(value)) ?: parseValue(value)
826 }
827
valueEqualsnull828 override fun valueEquals(value: Array<String>?, other: Array<String>?) =
829 value.contentDeepEquals(other)
830
831 // "null" is still serialized as "null"
832 override fun serializeAsValues(value: Array<String>?): List<String> =
833 value?.map { NavUriUtils.encode(it) } ?: emptyList()
834
emptyCollectionnull835 override fun emptyCollection(): Array<String> = arrayOf()
836 }
837
838 internal class StringListNavType : CollectionNavType<List<String>?>(true) {
839 override val name: String
840 get() = "List<String>"
841
842 override fun put(bundle: SavedState, key: String, value: List<String>?) {
843 bundle.write {
844 if (value != null) putStringArray(key, value.toTypedArray()) else putNull(key)
845 }
846 }
847
848 @Suppress("UNCHECKED_CAST", "DEPRECATION")
849 override fun get(bundle: SavedState, key: String): List<String>? {
850 return bundle.read {
851 if (!contains(key) || isNull(key)) null else getStringArray(key).toList()
852 }
853 }
854
855 override fun parseValue(value: String): List<String> {
856 return listOf(value)
857 }
858
859 override fun parseValue(value: String, previousValue: List<String>?): List<String>? {
860 return previousValue?.plus(parseValue(value)) ?: parseValue(value)
861 }
862
863 override fun valueEquals(value: List<String>?, other: List<String>?): Boolean {
864 val valueArray = value?.toTypedArray()
865 val otherArray = other?.toTypedArray()
866 return valueArray.contentDeepEquals(otherArray)
867 }
868
869 override fun serializeAsValues(value: List<String>?): List<String> =
870 value?.map { NavUriUtils.encode(it) } ?: emptyList()
871
872 override fun emptyCollection(): List<String> = emptyList()
873 }
874