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