Skip to content

fix(navigation): make screen back navigation pop only its own entry - #56

Merged
aoreshkov merged 1 commit into
mainfrom
fix/idempotent-back-navigation
Oct 9, 2026
Merged

aoreshkov merged 1 commit into
mainfrom
fix/idempotent-back-navigation

Conversation

@aoreshkov

Copy link
Copy Markdown
Owner

Why

Every screen's back callback called Navigator.goBack(), which pops whatever screen is current. A second call from the same screen could therefore pop the screen beneath it. Two ways that can happen:

For example, leaving the edit screen could also leave the details screen. At the root of a non-start section (Settings) it was worse. The first call switches to the start section, and the second pops whatever that section was drilled into, which the user never touched.

This was worked out from the code, not reproduced in the app.

What changed

  • New method: Navigator.goBack(from: NavKey) acts only while from is the current top entry. Otherwise it does nothing. A second request from the same screen finds a different entry on top and is a no-op.
  • Call sites: the posting details and edit entries and the settings entry pass their own route.
  • System back: NavDisplay.onBack keeps the plain goBack(), because Navigation 3 calls it once per popped entry.
  • CLAUDE.md: the navigation section now records the rule.
  • API dump: core:navigation gains one public function.

Considered, not included

dropUnlessResumed on navigating clicks is the pattern in Android's UI events guidance. It works here:

  • Navigation 3 limits each entry's lifecycle to STARTED while it animates in or out (release notes, 1.0.0-rc01 and 1.1.0-rc01).
  • The JetBrains navigation3-ui 1.1.2 that this project uses does the same in common code.

It complements this change rather than replacing it:

  • It also covers double taps on forward navigation (a list row, the edit button), which this change cannot.
  • It does not cover navigation triggered by state, such as the save and delete flags, because a LaunchedEffect is not a click.

It touches every navigating click, so it's left for a separate change.

Verification

./gradlew check passes. Five new NavigatorTest cases:

  • the top entry pops;
  • a repeated call pops only once;
  • an entry that is not on top is a no-op;
  • an entry in another section is a no-op;
  • at a section root, the call returns to the start section only once.

🤖 Generated with Claude Code

Navigator.goBack() pops whatever is current, and every screen's back
callback called it unconditionally. A second call - a double tap on the
back arrow while the screen animates out, or a tap racing the save/delete
navigation - could pop the screen beneath instead: back from edit could also
leave details. At a non-start section root the repeat was worse: the first
call switched to the start section, the second would pop whatever that
section was drilled into.

Add goBack(from: NavKey), which acts only while `from` is the current top
entry, and pass each screen's own route from the posting and settings
navigation entries. System back (NavDisplay.onBack, invoked once per popped
entry) keeps the unkeyed goBack().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown

Code Coverage

Overall Project 98.47% 🍏
Files changed 100% 🍏

File Coverage
Navigator.kt 100% 🍏

@aoreshkov
aoreshkov merged commit cdf19e9 into main Oct 9, 2026
14 checks passed
@aoreshkov
aoreshkov deleted the fix/idempotent-back-navigation branch October 9, 2026 05:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant