Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.flipcash.app.core.extensions

import androidx.compose.runtime.snapshots.Snapshot
import androidx.navigation3.runtime.NavKey
import com.flipcash.app.core.AppRoute
import com.getcode.navigation.core.CodeNavigator
Expand Down Expand Up @@ -39,10 +40,12 @@ fun CodeNavigator.navigateTo(routes: List<NavKey>, options: NavOptions = NavOpti
// The callback is invoked by ModalBottomSheetScene after the dismiss
// animation completes and the old entry is removed from the backstack.
pendingSheetDismiss = {
sheetGeneration++
resolved.forEachIndexed { index, route ->
val navOptions = if (index == 0) options else NavOptions()
navigate(route, navOptions)
Snapshot.withMutableSnapshot {
sheetGeneration++
resolved.forEachIndexed { index, route ->
val navOptions = if (index == 0) options else NavOptions()
navigate(route, navOptions)
}
}
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,5 +226,55 @@ class NavigateToTest {
}
}

@Test
fun `navigate deduplicates identical sheet that was not removed by onBack`() {
// Reproduces the production crash: a stale Sheet(Wallet,[]) remains on the
// backstack after onBack removed the wrong entry during a dismiss animation.
// A subsequent navigate for the same Sheet must not produce a duplicate.
val navigator = createNavigator(
AppRoute.Main.Scanner,
AppRoute.Main.Sheet(AppRoute.Sheets.Wallet),
)

// Simulate: something pushed on top during dismiss, onBack removed that
// instead of the sheet, so the old sheet is still here.
// Now navigate to the same sheet again.
navigator.navigate(
AppRoute.Main.Sheet(AppRoute.Sheets.Wallet),
NavOptions(debugRouting = false),
)

val sheets = navigator.backStack.filterIsInstance<AppRoute.Main.Sheet>()
assertEquals(1, sheets.size, "Expected exactly one Sheet on the backstack")
}

@Test
fun `double navigateTo with pending dismiss does not produce duplicate sheets`() {
val navigator = createNavigator(
AppRoute.Main.Scanner,
AppRoute.Main.Sheet(AppRoute.Sheets.Wallet),
)

// First navigate sets pendingSheetDismiss
navigator.navigateTo(listOf(AppRoute.Sheets.Menu), options = quietOptions)
assertNotNull(navigator.pendingSheetDismiss)

// Second navigate overwrites pendingSheetDismiss
navigator.navigateTo(listOf(AppRoute.Sheets.Menu), options = quietOptions)

// Simulate: onBack removes old sheet, then callback fires
navigator.backStack.removeAt(navigator.backStack.lastIndex)
navigator.pendingSheetDismiss!!.invoke()

// Simulate a stale callback also firing navigate for the same sheet
navigator.navigate(
AppRoute.Main.Sheet(AppRoute.Sheets.Menu),
NavOptions(debugRouting = false),
)

val sheets = navigator.backStack.filterIsInstance<AppRoute.Main.Sheet>()
assertEquals(1, sheets.size, "Expected exactly one Sheet on the backstack")
}

// endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ data class CodeNavigator(
if (backStack.isNotEmpty()) backStack.removeAt(backStack.lastIndex)
}
if (route is Sheet) {
backStack.removeAll { it == route }
backStack.removeAll { it is Sheet && it.toString() == route.toString() }
}
backStack.add(route)
}
Expand All @@ -126,10 +126,10 @@ data class CodeNavigator(
if (route is Sheet || currentRouteKey != route) {
// Sheet routes must be unique on the backstack —
// SaveableStateProvider uses contentKey (toString()) and
// crashes on duplicates. Remove any existing instance
// before pushing the new one.
// crashes on duplicates. Remove any entry whose contentKey
// (toString()) matches before pushing the new one.
if (route is Sheet) {
backStack.removeAll { it == route }
backStack.removeAll { it is Sheet && it.toString() == route.toString() }
}
backStack.add(route)
}
Expand All @@ -139,7 +139,7 @@ data class CodeNavigator(
Snapshot.withMutableSnapshot {
if (route is Sheet || currentRouteKey != route) {
if (route is Sheet) {
backStack.removeAll { it == route }
backStack.removeAll { it is Sheet && it.toString() == route.toString() }
}
backStack.add(route)
val lastIndex = backStack.lastIndex - 1
Expand Down
Loading