Skip to content

Commit eb06a5b

Browse files
committed
Add support for List<Enum> arguments in typesafe navigation
Test: ./gradlew navigation:navigation-common:test Test: ./gradlew navigation:navigation-common:cC Test: ./gradlew navigation:navigation-runtime:cC Bug: 375559962
1 parent 089d840 commit eb06a5b

File tree

4 files changed

+135
-0
lines changed

4 files changed

+135
-0
lines changed

navigation/navigation-common/src/androidTest/java/androidx/navigation/serialization/RouteFilledTest.kt

+26
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,32 @@ class RouteFilledTest {
841841
}
842842
assertThatRouteFilledFrom(clazz, listOf(arg)).isEqualTo("$PATH_SERIAL_NAME")
843843
}
844+
845+
@Test
846+
fun encodeEnumList() {
847+
@Serializable @SerialName(PATH_SERIAL_NAME) class TestClass(val arg: List<TestEnum>)
848+
849+
val clazz = TestClass(listOf(TestEnum.ONE, TestEnum.TWO))
850+
val arg =
851+
navArgument("arg") {
852+
type = InternalNavType.EnumListType(TestEnum::class.java)
853+
nullable = true
854+
}
855+
assertThatRouteFilledFrom(clazz, listOf(arg)).isEqualTo("$PATH_SERIAL_NAME?arg=ONE&arg=TWO")
856+
}
857+
858+
@Test
859+
fun encodeEnumListNullable() {
860+
@Serializable @SerialName(PATH_SERIAL_NAME) class TestClass(val arg: List<TestEnum>?)
861+
862+
val clazz = TestClass(null)
863+
val arg =
864+
navArgument("arg") {
865+
type = InternalNavType.EnumListType(TestEnum::class.java)
866+
nullable = true
867+
}
868+
assertThatRouteFilledFrom(clazz, listOf(arg)).isEqualTo(PATH_SERIAL_NAME)
869+
}
844870
}
845871

846872
private fun <T : Any> assertThatRouteFilledFrom(

navigation/navigation-common/src/main/java/androidx/navigation/serialization/NavTypeConverter.kt

+45
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ internal fun SerialDescriptor.getNavType(): NavType<*> {
101101
InternalType.LONG -> NavType.LongListType
102102
InternalType.STRING -> NavType.StringListType
103103
InternalType.STRING_NULLABLE -> InternalNavType.StringNullableListType
104+
InternalType.ENUM ->
105+
@Suppress("UNCHECKED_CAST")
106+
InternalNavType.EnumListType(
107+
getElementDescriptor(0).getClass() as Class<Enum<*>>
108+
)
104109
else -> UNKNOWN
105110
}
106111
}
@@ -471,6 +476,46 @@ internal object InternalNavType {
471476
override fun emptyCollection(): List<Double> = emptyList()
472477
}
473478

479+
class EnumListType<D : Enum<*>>(type: Class<D>) : CollectionNavType<List<D>?>(true) {
480+
private val enumNavType = EnumType(type)
481+
482+
override val name: String
483+
get() = "List<${enumNavType.name}}>"
484+
485+
override fun put(bundle: Bundle, key: String, value: List<D>?) {
486+
bundle.putSerializable(key, value?.let { ArrayList(value) })
487+
}
488+
489+
@Suppress("DEPRECATION", "UNCHECKED_CAST")
490+
override fun get(bundle: Bundle, key: String): List<D>? = (bundle[key] as? List<D>?)
491+
492+
override fun parseValue(value: String): List<D> = listOf(enumNavType.parseValue(value))
493+
494+
override fun parseValue(value: String, previousValue: List<D>?): List<D>? =
495+
previousValue?.plus(parseValue(value)) ?: parseValue(value)
496+
497+
override fun valueEquals(value: List<D>?, other: List<D>?): Boolean {
498+
val valueArrayList = value?.let { ArrayList(value) }
499+
val otherArrayList = other?.let { ArrayList(other) }
500+
return valueArrayList == otherArrayList
501+
}
502+
503+
override fun serializeAsValues(value: List<D>?): List<String> =
504+
value?.map { it.toString() } ?: emptyList()
505+
506+
override fun emptyCollection(): List<D> = emptyList()
507+
508+
public override fun equals(other: Any?): Boolean {
509+
if (this === other) return true
510+
if (other !is EnumListType<*>) return false
511+
return enumNavType == other.enumNavType
512+
}
513+
514+
public override fun hashCode(): Int {
515+
return enumNavType.hashCode()
516+
}
517+
}
518+
474519
class EnumNullableType<D : Enum<*>?>(type: Class<D?>) : SerializableNullableType<D?>(type) {
475520
private val type: Class<D?>
476521

navigation/navigation-common/src/test/java/androidx/navigation/serialization/NavArgumentGeneratorTest.kt

+28
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,34 @@ class NavArgumentGeneratorTest {
652652
assertThat(converted[0].argument.isDefaultValueUnknown).isFalse()
653653
}
654654

655+
@Test
656+
fun convertToEnumList() {
657+
@Serializable class TestClass(val arg: List<TestEnum>)
658+
659+
val converted = serializer<TestClass>().generateNavArguments()
660+
val expected =
661+
navArgument("arg") {
662+
type = InternalNavType.EnumListType(TestEnum::class.java)
663+
nullable = false
664+
}
665+
assertThat(converted).containsExactlyInOrder(expected)
666+
assertThat(converted[0].argument.isDefaultValueUnknown).isFalse()
667+
}
668+
669+
@Test
670+
fun convertToEnumListNullable() {
671+
@Serializable class TestClass(val arg: List<TestEnum>?)
672+
673+
val converted = serializer<TestClass>().generateNavArguments()
674+
val expected =
675+
navArgument("arg") {
676+
type = InternalNavType.EnumListType(TestEnum::class.java)
677+
nullable = true
678+
}
679+
assertThat(converted).containsExactlyInOrder(expected)
680+
assertThat(converted[0].argument.isDefaultValueUnknown).isFalse()
681+
}
682+
655683
@Test
656684
fun convertToParcelable() {
657685
@Serializable

navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt

+36
Original file line numberDiff line numberDiff line change
@@ -5250,6 +5250,42 @@ class NavControllerRouteTest {
52505250
assertThat(route2!!.arg).containsExactly(11E123, 11.11)
52515251
}
52525252

5253+
@UiThreadTest
5254+
@Test
5255+
fun testNavigateWithObjectEnumList() {
5256+
@Serializable @SerialName("test") class TestClass(val arg: List<TestEnum>)
5257+
5258+
val navController = createNavController()
5259+
navController.graph =
5260+
navController.createGraph(
5261+
startDestination = TestClass(listOf(TestEnum.ONE, TestEnum.TWO))
5262+
) {
5263+
test<TestClass>()
5264+
}
5265+
assertThat(navController.currentDestination?.route).isEqualTo("test?arg={arg}")
5266+
val route = navController.currentBackStackEntry?.toRoute<TestClass>()
5267+
assertThat(route!!.arg).containsExactly(TestEnum.ONE, TestEnum.TWO)
5268+
}
5269+
5270+
@UiThreadTest
5271+
@Test
5272+
fun testNavigateWithObjectNullEnumList() {
5273+
@Serializable @SerialName("test") class TestClass(val arg: List<TestEnum>? = null)
5274+
5275+
val navController = createNavController()
5276+
navController.graph =
5277+
navController.createGraph(startDestination = TestClass(null)) { test<TestClass>() }
5278+
5279+
assertThat(navController.currentDestination?.route).isEqualTo("test?arg={arg}")
5280+
val route = navController.currentBackStackEntry?.toRoute<TestClass>()
5281+
assertThat(route!!.arg).isNull()
5282+
5283+
navController.navigate(TestClass(listOf(TestEnum.ONE, TestEnum.TWO)))
5284+
assertThat(navController.currentDestination?.route).isEqualTo("test?arg={arg}")
5285+
val route2 = navController.currentBackStackEntry?.toRoute<TestClass>()
5286+
assertThat(route2!!.arg).containsExactly(TestEnum.ONE, TestEnum.TWO)
5287+
}
5288+
52535289
@UiThreadTest
52545290
@Test
52555291
fun testNavigateWithObjectValueClass() {

0 commit comments

Comments
 (0)