From 88c9bbd7193c44827d98b87bc9d25410dbdcda38 Mon Sep 17 00:00:00 2001 From: amirmohammad Date: Tue, 17 Feb 2026 10:30:39 +0330 Subject: [PATCH] Fix incorrect back navigation to ForYou instead of Topic screen (#1866) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When navigating ForYou → Topic → switch tab → back, the user landed on the ForYou screen instead of the Topic screen. The root cause was that toEntries() flattened all tab sub-stacks into a single entries list, causing ListDetailSceneStrategy to mis-render after cross-tab back. - Change toEntries() to only return entries from the current tab's sub-stack while still decorating all stacks for state preservation - Add BackHandler for cross-tab back navigation since NavDisplay now only sees within-tab entries - Add unit test for the sub-stack preservation scenario - Un-ignore navigationBar_multipleBackStackInterests test - Add back-button variant instrumented test --- .../apps/nowinandroid/ui/NavigationTest.kt | 30 ++++++++++++++++--- .../samples/apps/nowinandroid/ui/NiaApp.kt | 11 ++++++- .../core/navigation/NavigationState.kt | 4 +-- .../core/navigation/NavigatorTest.kt | 26 ++++++++++++++++ 4 files changed, 63 insertions(+), 8 deletions(-) diff --git a/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NavigationTest.kt b/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NavigationTest.kt index c0eba5fd3..a02490090 100644 --- a/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NavigationTest.kt +++ b/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NavigationTest.kt @@ -46,7 +46,6 @@ import dagger.hilt.android.testing.HiltAndroidTest import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test import javax.inject.Inject @@ -255,9 +254,6 @@ class NavigationTest { } } - // TODO decide if backStack should preserve previous stacks when navigating back to home tab (ForYou) - // https://github.com/android/nowinandroid/issues/1937 - @Ignore @Test fun navigationBar_multipleBackStackInterests() { composeTestRule.apply { @@ -283,6 +279,32 @@ class NavigationTest { } } + @Test + fun navigationBar_backFromTabPreservesSubStack() { + composeTestRule.apply { + onNodeWithText(interests).performClick() + + // Select a topic + val topic = runBlocking { + topicsRepository.getTopics().first().sortedBy(Topic::name).last() + } + onNodeWithTag(LIST_PANE_TEST_TAG).performScrollToNode(hasText(topic.name)) + onNodeWithText(topic.name).performClick() + + // Verify the topic is shown + onNodeWithTag("topic:${topic.id}").assertIsDisplayed() + + // Switch to another tab + onNodeWithText(saved).performClick() + + // Press back to return to previous tab + Espresso.pressBack() + + // Verify the topic detail is still shown (sub-stack preserved) + onNodeWithTag("topic:${topic.id}").assertExists() + } + } + @Test fun navigatingToTopicFromForYou_showsTopicDetails() { composeTestRule.apply { diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt index bfaa27fa6..38d29a9a7 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt @@ -16,6 +16,7 @@ package com.google.samples.apps.nowinandroid.ui +import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.WindowInsets @@ -264,8 +265,16 @@ internal fun NiaApp( searchEntry(navigator) } + val navigationState = appState.navigationState + BackHandler( + enabled = navigationState.currentKey == navigationState.currentTopLevelKey && + navigationState.currentTopLevelKey != navigationState.startKey, + ) { + navigator.goBack() + } + NavDisplay( - entries = appState.navigationState.toEntries(entryProvider), + entries = navigationState.toEntries(entryProvider), sceneStrategy = listDetailStrategy, onBack = { navigator.goBack() }, ) diff --git a/core/navigation/src/main/kotlin/com/google/samples/apps/nowinandroid/core/navigation/NavigationState.kt b/core/navigation/src/main/kotlin/com/google/samples/apps/nowinandroid/core/navigation/NavigationState.kt index 864fec794..e0d8149f3 100644 --- a/core/navigation/src/main/kotlin/com/google/samples/apps/nowinandroid/core/navigation/NavigationState.kt +++ b/core/navigation/src/main/kotlin/com/google/samples/apps/nowinandroid/core/navigation/NavigationState.kt @@ -96,7 +96,5 @@ fun NavigationState.toEntries( ) } - return topLevelStack - .flatMap { decoratedEntries[it] ?: emptyList() } - .toMutableStateList() + return (decoratedEntries[currentTopLevelKey] ?: emptyList()).toMutableStateList() } diff --git a/core/navigation/src/test/kotlin/com/google/samples/apps/nowinandroid/core/navigation/NavigatorTest.kt b/core/navigation/src/test/kotlin/com/google/samples/apps/nowinandroid/core/navigation/NavigatorTest.kt index 86c4acc25..9a2e7c44c 100644 --- a/core/navigation/src/test/kotlin/com/google/samples/apps/nowinandroid/core/navigation/NavigatorTest.kt +++ b/core/navigation/src/test/kotlin/com/google/samples/apps/nowinandroid/core/navigation/NavigatorTest.kt @@ -247,6 +247,32 @@ class NavigatorTest { assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey) } + @Test + fun testSubStackPreservedAfterTabSwitchAndBack() { + // Navigate to sub-page in first tab (ForYou → Topic) + navigator.navigate(TestKeyFirst) + + assertThat(navigationState.currentKey).isEqualTo(TestKeyFirst) + assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey) + + // Switch to second tab (e.g. Bookmarks) + navigator.navigate(TestSecondTopLevelKey) + + assertThat(navigationState.currentKey).isEqualTo(TestSecondTopLevelKey) + assertThat(navigationState.currentTopLevelKey).isEqualTo(TestSecondTopLevelKey) + + // Press back from second tab + navigator.goBack() + + // Should return to first tab with sub-stack preserved + assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey) + assertThat(navigationState.currentKey).isEqualTo(TestKeyFirst) + assertThat(navigationState.currentSubStack).containsExactly( + TestFirstTopLevelKey, + TestKeyFirst, + ).inOrder() + } + @Test fun throwOnEmptyBackStack() { assertFailsWith {