Merge "GetBackStack to throw if route not in graph" into androidx-main
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
index 34c0450e..a308c35 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
@@ -1484,6 +1484,79 @@
 
     @UiThreadTest
     @Test
+    fun testGetBackStackEntryWithKClassNested() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            // a sibling graph with nested KClass destination
+            navigation<TestGraph>(startDestination = TestClass::class) {
+                test<TestClass>()
+            }
+            test("second")
+        }
+
+        navController.navigate(TestClass())
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navigator.backStack.size).isEqualTo(2)
+        assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo(
+            TEST_CLASS_ROUTE
+        )
+
+        navController.navigate("second")
+
+        assertThat(navigator.backStack.size).isEqualTo(3)
+        assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo("second")
+
+        val entry = navController.getBackStackEntry<TestClass>()
+        assertThat(entry.destination.route).isEqualTo(TEST_CLASS_ROUTE)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testGetBackStackEntryWithKClassNotInGraph() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test<TestClass>()
+        }
+        navController.navigate(TestClass())
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navigator.backStack.size).isEqualTo(2)
+
+        val exception = assertFailsWith<IllegalArgumentException> {
+            navController.getBackStackEntry<TestClassPathArg>()
+        }
+        assertThat(exception.message).isEqualTo(
+            "Destination with route TestClassPathArg cannot be found in " +
+                "navigation graph ${navController.graph}"
+        )
+    }
+
+    @UiThreadTest
+    @Test
+    fun testGetBackStackEntryWithKClassNotInBackstack() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test<TestClass>()
+        }
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navigator.backStack.size).isEqualTo(1)
+
+        val exception = assertFailsWith<IllegalArgumentException> {
+            navController.getBackStackEntry<TestClass>()
+        }
+        assertThat(exception.message).isEqualTo(
+            "No destination with route TestClass is on the NavController's " +
+                "back stack. The current destination is ${navController.currentDestination}"
+        )
+    }
+
+    @UiThreadTest
+    @Test
     fun testGetBackStackEntryWithObject() {
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
@@ -1561,6 +1634,79 @@
 
     @UiThreadTest
     @Test
+    fun testGetBackStackEntryWithObjectNested() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            // a sibling graph with nested KClass destination
+            navigation<TestGraph>(startDestination = TestClassPathArg::class) {
+                test<TestClassPathArg>()
+            }
+            test("second")
+        }
+
+        navController.navigate(TestClassPathArg(1))
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navigator.backStack.size).isEqualTo(2)
+        assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo(
+            TEST_CLASS_PATH_ARG_ROUTE
+        )
+
+        navController.navigate("second")
+
+        assertThat(navigator.backStack.size).isEqualTo(3)
+        assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo("second")
+
+        val entry = navController.getBackStackEntry(TestClassPathArg(1))
+        assertThat(entry.destination.route).isEqualTo(TEST_CLASS_PATH_ARG_ROUTE)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testGetBackStackEntryWithObjectNotInGraph() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test<TestClass>()
+        }
+        navController.navigate(TestClass())
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navigator.backStack.size).isEqualTo(2)
+
+        val exception = assertFailsWith<IllegalArgumentException> {
+            navController.getBackStackEntry(TestClassPathArg(1))
+        }
+        assertThat(exception.message).isEqualTo(
+            "Destination with route TestClassPathArg cannot be found in " +
+                "navigation graph ${navController.graph}"
+        )
+    }
+
+    @UiThreadTest
+    @Test
+    fun testGetBackStackEntryWithObjectNotInBackstack() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test<TestClass>()
+        }
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navigator.backStack.size).isEqualTo(1)
+
+        val exception = assertFailsWith<IllegalArgumentException> {
+            navController.getBackStackEntry(TestClass())
+        }
+        assertThat(exception.message).isEqualTo(
+            "No destination with route $TEST_CLASS_ROUTE is on the NavController's " +
+                "back stack. The current destination is ${navController.currentDestination}"
+        )
+    }
+
+    @UiThreadTest
+    @Test
     fun testPopBackStack() {
         val navController = createNavController()
         navController.graph = nav_singleArg_graph
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 c20f5fd..d0b7a2e 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
@@ -2699,8 +2699,21 @@
      * target NavBackStackEntry's [NavDestination] must have been created with route from [KClass].
      * @throws IllegalArgumentException if the destination is not on the back stack
      */
-    public inline fun <reified T : Any> getBackStackEntry(): NavBackStackEntry =
-        getBackStackEntry(serializer<T>().hashCode())
+    public inline fun <reified T : Any> getBackStackEntry(): NavBackStackEntry {
+        val id = serializer<T>().hashCode()
+        requireNotNull(findDestinationFromRoot(id)) {
+            "Destination with route ${T::class.simpleName} cannot be found in navigation " +
+                "graph $graph"
+        }
+        val lastFromBackStack = currentBackStack.value.lastOrNull { entry ->
+            entry.destination.id == id
+        }
+        requireNotNull(lastFromBackStack) {
+            "No destination with route ${T::class.simpleName} is on the NavController's " +
+                "back stack. The current destination is $currentDestination"
+        }
+        return lastFromBackStack
+    }
 
     /**
      * Gets the topmost [NavBackStackEntry] for a route from an Object.
@@ -2717,10 +2730,6 @@
         // route contains arguments so we need to generate the populated route
         // rather than getting entry based on route pattern
         val finalRoute = generateRouteFilled(route)
-        requireNotNull(finalRoute) {
-            "No destination with route $finalRoute is on the NavController's back stack. The " +
-                "current destination is $currentDestination"
-        }
         return getBackStackEntry(finalRoute)
     }