NavTypeConverter.kt
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:OptIn(ExperimentalSerializationApi::class)
package androidx.navigation.serialization
import android.os.Bundle
import androidx.navigation.NavType
import kotlin.reflect.KType
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.serializer
/**
* Marker for Native Kotlin types with either full or partial built-in NavType support
*/
private enum class InternalType {
INT,
BOOL,
FLOAT,
LONG,
STRING,
INT_ARRAY,
BOOL_ARRAY,
FLOAT_ARRAY,
LONG_ARRAY,
ARRAY,
LIST,
UNKNOWN
}
/**
* Converts an argument type to a built-in NavType.
*
* Built-in NavTypes include NavType objects declared within [NavType.Companion], such as
* [NavType.IntType], [NavType.BoolArrayType] etc.
*
* Returns [UNKNOWN] type if the argument does not have built-in NavType support.
*/
internal fun SerialDescriptor.getNavType(): NavType<*> {
val type = when (this.toInternalType()) {
InternalType.INT -> NavType.IntType
InternalType.BOOL -> NavType.BoolType
InternalType.FLOAT -> NavType.FloatType
InternalType.LONG -> NavType.LongType
InternalType.STRING -> NavType.StringType
InternalType.INT_ARRAY -> NavType.IntArrayType
InternalType.BOOL_ARRAY -> NavType.BoolArrayType
InternalType.FLOAT_ARRAY -> NavType.FloatArrayType
InternalType.LONG_ARRAY -> NavType.LongArrayType
InternalType.ARRAY -> {
val typeParameter = getElementDescriptor(0).toInternalType()
if (typeParameter == InternalType.STRING) NavType.StringArrayType else UNKNOWN
}
InternalType.LIST -> {
val typeParameter = getElementDescriptor(0).toInternalType()
when (typeParameter) {
InternalType.INT -> NavType.IntListType
InternalType.BOOL -> NavType.BoolListType
InternalType.FLOAT -> NavType.FloatListType
InternalType.LONG -> NavType.LongListType
InternalType.STRING -> NavType.StringListType
else -> UNKNOWN
}
}
else -> UNKNOWN
}
return type
}
/**
* Convert SerialDescriptor to an InternalCommonType.
*
* The descriptor's associated argument could be any of the native Kotlin types supported
* in [InternalType], or it could be an unsupported type (custom class, object or enum).
*/
private fun SerialDescriptor.toInternalType(): InternalType {
val serialName = serialName.replace("?", "")
return when {
serialName == "kotlin.Int" -> InternalType.INT
serialName == "kotlin.Boolean" -> InternalType.BOOL
serialName == "kotlin.Float" -> InternalType.FLOAT
serialName == "kotlin.Long" -> InternalType.LONG
serialName == "kotlin.String" -> InternalType.STRING
serialName == "kotlin.IntArray" -> InternalType.INT_ARRAY
serialName == "kotlin.BooleanArray" -> InternalType.BOOL_ARRAY
serialName == "kotlin.FloatArray" -> InternalType.FLOAT_ARRAY
serialName == "kotlin.LongArray" -> InternalType.LONG_ARRAY
serialName == "kotlin.Array" -> InternalType.ARRAY
// serial name for both List and ArrayList
serialName.startsWith("kotlin.collections.ArrayList") -> InternalType.LIST
// custom classes or other types without built-in NavTypes
else -> InternalType.UNKNOWN
}
}
/**
* Match the [SerialDescriptor] of a type to a KType
*
* Returns true match, false otherwise.
*/
internal fun SerialDescriptor.matchKType(kType: KType): Boolean {
if (this.isNullable != kType.isMarkedNullable) return false
if (this.hashCode() != serializer(kType).descriptor.hashCode()) return false
return true
}
internal object UNKNOWN : NavType<String>(false) {
override val name: String
get() = "unknown"
override fun put(bundle: Bundle, key: String, value: String) {}
override fun get(bundle: Bundle, key: String): String? = null
override fun parseValue(value: String): String = "null"
}