Fix missing non-nullable arg when rebuilding hierarchy
When navigating with NavDirections, args is populated with an empty bundle. This causes issue when we rebuild parent hierarchy while adding a new entry to NavBackStack. If the Entry being rebuilt contains a non-nullalbe arg, i.e. Long, this empty bundle will cause an exception.
Test: ./gradlew navigation:navigation-runtime:cC
Bug: 249988437
Change-Id: I5c8ce739ad9a3428c8a8de13eae391bfff0db5df
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
index abb391c..5e04fc2 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
@@ -58,6 +58,7 @@
import androidx.testutils.test
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import kotlin.test.assertFailsWith
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.take
@@ -104,6 +105,22 @@
}
}
+ private val NESTED_NAV_GRAPH_2 =
+ navController.createGraph(route = "graph", startDestination = "dest1") {
+ test("dest1")
+ navigation(route = "nested", startDestination = "dest2") {
+ argument("longArg") {
+ type = NavType.LongType
+ }
+ test("dest2") {
+ argument("longArg") {
+ type = NavType.LongType
+ }
+ }
+ test("dest3")
+ }
+ }
+
@UiThreadTest
@Test
fun testGetCurrentBackStackEntry() {
@@ -1653,6 +1670,23 @@
@UiThreadTest
@Test
+ fun testNavigateWithMissingNonNullableArg() {
+ val navController = createNavController()
+ navController.graph = NESTED_NAV_GRAPH_2
+ assertThat(navController.currentDestination?.route).isEqualTo("dest1")
+
+ val nestedId = ("android-app://androidx.navigation/nested").hashCode()
+
+ val expected = assertFailsWith<NullPointerException> {
+ navController.navigate(nestedId)
+ }
+ assertThat(expected.message).isEqualTo(
+ "null cannot be cast to non-null type kotlin.Long"
+ )
+ }
+
+ @UiThreadTest
+ @Test
fun testNavigateMultipleParentsOnHierarchy() {
val navController = createNavController()
navController.setGraph(R.navigation.nav_root)
@@ -1670,6 +1704,34 @@
@UiThreadTest
@Test
+ fun testRebuildParentWithMissingNonNullableArg() {
+ val navController = createNavController()
+ navController.graph = NESTED_NAV_GRAPH_2
+ assertThat(navController.currentDestination?.route).isEqualTo("dest1")
+
+ val nestedId1 = ("android-app://androidx.navigation/nested").hashCode()
+
+ // navigate to nested graph first destination, provide non-nullable arg
+ navController.navigate(
+ nestedId1,
+ bundleOf("longArg" to 123L)
+ )
+ assertThat(navController.currentDestination?.route).isEqualTo("dest2")
+
+ val nestedId2 = ("android-app://androidx.navigation/dest3").hashCode()
+ // navigate to nested graph second destination after popping up to graph (inclusive)
+ // empty bundle to imitate navigating with NavDirections
+ navController.navigate(
+ nestedId2,
+ Bundle(),
+ NavOptions.Builder().setPopUpTo("nested", inclusive = true).build()
+ )
+ // [graph, dest1, nested, dest3]
+ assertThat(navController.currentBackStack.value.size).isEqualTo(4)
+ }
+
+ @UiThreadTest
+ @Test
fun testNavigateWithOverriddenDefaultArgs() {
val args = Bundle()
args.putString(TEST_OVERRIDDEN_VALUE_ARG, TEST_OVERRIDDEN_VALUE_ARG_VALUE)
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
index 9c54f99..302dc96 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
@@ -2045,10 +2045,11 @@
while (destination != null && findDestination(destination.id) !== destination) {
val parent = destination.parent
if (parent != null) {
+ val args = if (finalArgs?.isEmpty == true) null else finalArgs
val entry = restoredEntries.lastOrNull { restoredEntry ->
restoredEntry.destination == parent
} ?: NavBackStackEntry.create(
- context, parent, parent.addInDefaultArgs(finalArgs), hostLifecycleState,
+ context, parent, parent.addInDefaultArgs(args), hostLifecycleState,
viewModel
)
hierarchy.addFirst(entry)