Merge "Fix entry not marked complete after view is destroyed" into snap-temp-L66700000963015280
diff --git a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt
index dc60ee0..5ae8346 100644
--- a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt
+++ b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt
@@ -171,6 +171,7 @@
         assertWithMessage("Replacement Fragment should be the primary navigation Fragment")
             .that(fragmentManager.primaryNavigationFragment)
             .isSameInstanceAs(replacementFragment)
+        assertThat(navigatorState.transitionsInProgress.value).isEmpty()
     }
 
     @UiThreadTest
@@ -359,6 +360,7 @@
         assertWithMessage("Fragment should be the primary navigation Fragment after pop")
             .that(fragmentManager.primaryNavigationFragment)
             .isSameInstanceAs(fragment)
+        assertThat(navigatorState.transitionsInProgress.value).isEmpty()
     }
 
     @UiThreadTest
@@ -528,6 +530,26 @@
         assertWithMessage("PrimaryFragment should be the correct type")
             .that(fragmentManager.primaryNavigationFragment)
             .isNotInstanceOf(EmptyFragment::class.java)
+        assertThat(navigatorState.transitionsInProgress.value).isEmpty()
+    }
+
+    @UiThreadTest
+    @Test
+    fun testMultipleNavigateFragmentTransactionsThenPopMultiple() {
+        val entry = createBackStackEntry()
+        val secondEntry = createBackStackEntry(SECOND_FRAGMENT, clazz = Fragment::class)
+        val thirdEntry = createBackStackEntry(THIRD_FRAGMENT)
+
+        // Push 3 fragments
+        fragmentNavigator.navigate(listOf(entry, secondEntry, thirdEntry), null, null)
+        fragmentManager.executePendingTransactions()
+
+        // Now pop multiple fragments with savedState so that the secondEntry does not get
+        // marked complete by clear viewModel
+        fragmentNavigator.popBackStack(secondEntry, true)
+        fragmentManager.executePendingTransactions()
+        assertThat(navigatorState.backStack.value).containsExactly(entry)
+        assertThat(navigatorState.transitionsInProgress.value).isEmpty()
     }
 
     @UiThreadTest
diff --git a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavControllerWithFragmentTest.kt b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavControllerWithFragmentTest.kt
index d000958..21e0c3c 100644
--- a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavControllerWithFragmentTest.kt
+++ b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavControllerWithFragmentTest.kt
@@ -74,6 +74,9 @@
         assertWithMessage("New Entry should be RESUMED")
             .that(navController.currentBackStackEntry!!.lifecycle.currentState)
             .isEqualTo(Lifecycle.State.RESUMED)
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry!!
+        )
     }
     @Ignore("b/276806142")
     @Test
@@ -205,6 +208,7 @@
         // ensure original Fragment is dismissed and backStacks are in sync
         assertThat(navigator.backStack.value.size).isEqualTo(1)
         assertThat(fm.fragments.size).isEqualTo(2) // start + dialog fragment
+        assertThat(navController.visibleEntries.value.size).isEqualTo(2)
     }
 
     @Test
@@ -298,6 +302,9 @@
         fm?.executePendingTransactions()
 
         assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo("first")
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry
+        )
     }
 
     @LargeTest
@@ -323,6 +330,9 @@
         onBackPressedDispatcher.onBackPressed()
 
         assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo("third")
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry!!
+        )
     }
 
     @LargeTest
@@ -352,6 +362,9 @@
         onBackPressedDispatcher.onBackPressed()
 
         assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo("fourth")
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry!!
+        )
     }
 
     private fun withNavigationActivity(
diff --git a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.kt b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.kt
index 3fb3248..0c8b113 100644
--- a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.kt
+++ b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.kt
@@ -85,16 +85,14 @@
                 entry.id == fragment.tag
             }
             if (entry != null) {
-                if (!state.backStack.value.contains(entry)) {
-                    if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
-                        Log.v(
-                            TAG,
-                            "Marking transition complete for entry $entry " +
-                                "due to fragment $source lifecycle reaching DESTROYED"
-                        )
-                    }
-                    state.markTransitionComplete(entry)
+                if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+                    Log.v(
+                        TAG,
+                        "Marking transition complete for entry $entry " +
+                            "due to fragment $source lifecycle reaching DESTROYED"
+                    )
                 }
+                state.markTransitionComplete(entry)
             }
         }
     }
@@ -113,19 +111,16 @@
                 }
                 state.markTransitionComplete(entry)
             }
-            // Once the lifecycle reaches DESTROYED, if the entry is not in the back stack, we can
-            // mark the transition complete
+            // Once the lifecycle reaches DESTROYED, we can mark the transition complete
             if (event == Lifecycle.Event.ON_DESTROY) {
-                if (!state.backStack.value.contains(entry)) {
-                    if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
-                        Log.v(
-                            TAG,
-                            "Marking transition complete for entry $entry due " +
-                                "to fragment $owner view lifecycle reaching DESTROYED"
-                        )
-                    }
-                    state.markTransitionComplete(entry)
+                if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+                    Log.v(
+                        TAG,
+                        "Marking transition complete for entry $entry due " +
+                            "to fragment $owner view lifecycle reaching DESTROYED"
+                    )
                 }
+                state.markTransitionComplete(entry)
             }
         }
     }
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 3b662ee..2ddc413 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
@@ -186,6 +186,9 @@
         assertThat(navigator.backStack.size)
             .isEqualTo(1)
         assertThat(originalViewModel.isCleared).isTrue()
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry
+        )
     }
 
     @UiThreadTest
@@ -215,6 +218,7 @@
         val newViewModel = ViewModelProvider(newBackStackEntry).get<TestAndroidViewModel>()
         assertThat(newBackStackEntry.id).isSameInstanceAs(originalBackStackEntry.id)
         assertThat(newViewModel).isSameInstanceAs(originalViewModel)
+        assertThat(navController.visibleEntries.value).containsExactly(newBackStackEntry)
     }
 
     @UiThreadTest
@@ -606,6 +610,9 @@
         navController.navigate(R.id.second_test)
         assertThat(navController.currentDestination?.id ?: 0).isEqualTo(R.id.second_test)
         assertThat(navigator.backStack.size).isEqualTo(2)
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry
+        )
     }
 
     @UiThreadTest
@@ -2028,6 +2035,7 @@
             .isFalse()
         assertThat(navController.currentDestination).isNull()
         assertThat(navigator.backStack.size).isEqualTo(0)
+        assertThat(navController.visibleEntries.value).isEmpty()
     }
 
     @UiThreadTest
@@ -2074,6 +2082,9 @@
             .isTrue()
         assertThat(navController.currentDestination?.id ?: 0).isEqualTo(R.id.start_test)
         assertThat(navigator.backStack.size).isEqualTo(1)
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry
+        )
     }
 
     @UiThreadTest
@@ -2092,6 +2103,9 @@
         navigator.popCurrent()
         assertThat(navController.currentDestination?.id ?: 0).isEqualTo(R.id.start_test)
         assertThat(navigator.backStack.size).isEqualTo(1)
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry
+        )
     }
 
     @UiThreadTest
@@ -2132,6 +2146,9 @@
         )
         assertThat(navController.currentDestination?.id ?: 0).isEqualTo(R.id.second_test)
         assertThat(navigator.backStack.size).isEqualTo(1)
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry
+        )
     }
 
     @UiThreadTest
@@ -2172,6 +2189,9 @@
             .isTrue()
         assertThat(navController.currentDestination?.id ?: 0).isEqualTo(R.id.start_test)
         assertThat(navigator.backStack.size).isEqualTo(1)
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry
+        )
     }
 
     @UiThreadTest
@@ -2229,6 +2249,9 @@
         navController.navigate(R.id.self)
         assertThat(navController.currentDestination?.id ?: 0).isEqualTo(R.id.second_test)
         assertThat(navigator.backStack.size).isEqualTo(2)
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry
+        )
     }
 
     @UiThreadTest
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 338a3d8..db02a04 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
@@ -127,8 +127,9 @@
      * whenever they change. If there is no visible [NavBackStackEntry], this will be set to an
      * empty list.
      *
-     * - `CREATED` entries are listed first and include all entries that have been popped from
-     * the back stack and are in the process of completing their exit transition
+     * - `CREATED` entries are listed first and include all entries that are in the process of
+     * completing their exit transition. Note that this can include entries that have been
+     * popped off the Navigation back stack.
      * - `STARTED` entries on the back stack are next and include all entries that are running
      * their enter transition and entries whose destination is partially covered by a
      * `FloatingWindow` destination