Fix incorrect back navigation to ForYou instead of Topic screen (#1866)

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
pull/2072/head
amirmohammad 2 weeks ago
parent ade8065442
commit 88c9bbd719

@ -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 {

@ -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() },
)

@ -96,7 +96,5 @@ fun NavigationState.toEntries(
)
}
return topLevelStack
.flatMap { decoratedEntries[it] ?: emptyList() }
.toMutableStateList()
return (decoratedEntries[currentTopLevelKey] ?: emptyList()).toMutableStateList()
}

@ -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<IllegalStateException> {

Loading…
Cancel
Save