Skip to content

fix(android): [Part 3] Stop attributing Nav3 data to screens without a SentryNavEffect - #6483

Open
0xadam-brown wants to merge 4 commits into
feat/nav3-lifecycle-2-return-transactionsfrom
fix/nav3-lifecycle-3-lease-withdrawal
Open

0xadam-brown wants to merge 4 commits into
feat/nav3-lifecycle-2-return-transactionsfrom
fix/nav3-lifecycle-3-lease-withdrawal

Conversation

@0xadam-brown

@0xadam-brown 0xadam-brown commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

PR Stack (Nav3 Lifecycle)


📜 Description

Stops attributing errors and spans from an Activity or ViewPager2 page without a SentryNavEffect to the most recently visited Nav3 root.

Prior (and wrong) assumption Problematic scenario Before After
All Activities or Fragments in an app are nav roots The user opens an Activity or ViewPager2 page without a SentryNavEffect The covered nav root keeps the lease. The new screen's errors pick up the old screen name and back stack, and its spans go to the old nav transaction The lease is withdrawn: the old data is removed from the scope, and the transaction is unbound but kept for the user's return

How this PR fixes it

Via the concept of lease "withdrawal". Activities or Fragments without a SentryNavEffect never register with the NavLeaseCoordinator, so it has to infer their presences by observing lifecycle signals emitted by all Activities in the app. It relies on the presence of either of two signals:

  1. another Activity resumes over the leaseholder, or
  2. the leaseholder pauses while its host Activity stays resumed.

When either fires, the coordinator unpublishes the leaseholder's nav data, unbinds its transaction (but leaves it running), and withdraws the lease without handing it to anyone.

See the discussion below if you're interested in the details (no obligation!).

Details

Assume Home Activity has a SentryNavEffect but Plain Activity doesn't:

 1. App goes to background:   Home ON_PAUSE ──────────────────▶ Home ON_STOP
 5. Covered by PlainActivity: Home ON_PAUSE ─▶ Plain ON_RESUME ─▶ Home ON_STOP
                                               ^^^^^^^^^^^^^^^
                                      Invisible to Home's SentryNavEffect

Case (1) was already handled correctly before this PR stack. In case (2), Home kept the nav lease, so spans produced by Plain were attached to Home's nav transaction, and errors thrown by Plain carried Home's nav context (screen name and back stack). The two cases need opposite handling: in (1), Home should keep its data, as it's still the last screen the user saw. In (2), Home's data no longer describes what's on screen.

Lease withdrawal

NavLeaseCoordinator now "withdraws" the lease in case (2): it unpublishes the leaseholder's nav context and unbinds its nav transaction without handing the lease to another nav root. The transaction keeps running. When the leaseholder resumes, it takes the lease back and republishes its nav context and its transaction (or, after #6482, a new one if the old one finished).

Nav roots without a SentryNavEffect never register, so the coordinator infers them from two signals:

  • Another Activity resumes while the leaseholder isn't resumed, unless that Activity is itself a nav root (which takes the lease on its own ON_RESUME). A new NavActivityWatcher reports every Activity resume to the coordinator, using the host Activity from NavRootInfo (ref(android): [Part 1] Refactor #6481).
  • The leaseholder pauses while its host Activity stays resumed, e.g., a ViewPager2 swipe to a page without an effect.

Also, a nav root that isn't resumed no longer takes the lease on its first back stack update while its host Activity is resumed. This covers a page preloaded next to a current page without an effect, which can't be the leaseholder.

Each withdrawal is logged at DEBUG with its reason, so misattributed nav data can be traced back to how the coordinator read the app's screens.

Limitations

Documented in the package README

  • Withdrawal happens only once the covering Activity resumes. Errors and spans the covering Activity produces during onCreate and onStart are still attributed to the leaseholder. For the same reason, the covering Activity's ui.load transaction can't bind while the leaseholder's nav transaction holds the scope, as before. The next PR in the stack withdraws the lease as soon as the covering Activity is created.
  • Leaseholders whose host Activity is unknown are never withdrawn.
  • Surfaces that cover the leaseholder without pausing it (e.g., dialogs, bottom sheets, Compose dialogs) aren't detected.

Sample app

Adds a Nav3PlainActivity reachable from the Nav3 screens, and a fourth ViewPager2 page without a SentryNavEffect.

💡 Motivation and Context

Errors and spans were attributed to the wrong screen in apps that mix Nav3 roots with Activities or ViewPager2 pages without a SentryNavEffect.

addresses: JAVA-785

💚 How did you test it?

  • Unit tests
  • Integration tests
  • Had my clanker thoroughly test the pre- and post-stack behavior via the Nav3 sample app i) to verify the stack really does what it claims to and ii) to ensure it doesn't introduce any regressions.

📝 Checklist

  • I added GH Issue ID & Linear ID
  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.
  • Public API changes reviewed by another Mobile SDK team member or implemented according to the develop docs spec.

🔮 Next steps

The next PR in the stack withdraws the lease when the covering Activity is created rather than when it resumes. On API 29+, that lets the covering Activity's ui.load transaction bind to the scope. It also retries a Nav3 transaction blocked by a closing Activity's ui.load.

⚠️ Merge this PR using a merge commit (not squash). Only the collection branch is squash-merged into main.

…out a SentryNavEffect

When an Activity or ViewPager2 page without a SentryNavEffect replaced the Nav3 leaseholder, the leaseholder kept its nav context and bound nav transaction, so errors and spans from the new screen were attributed to it. The coordinator now withdraws the lease in that case: it unpublishes the leaseholder's nav state and unbinds its transaction without handing the lease to another nav root. The leaseholder republishes them when it resumes.

Withdrawal is triggered when another Activity resumes while the leaseholder isn't resumed (unless that Activity is itself a nav root), or when the leaseholder pauses while its host Activity stays resumed (e.g., a ViewPager2 swipe). The host Activity comes from NavRootInfo, and a NavActivityWatcher reports Activity resumes. A nav root that isn't resumed also no longer takes the lease on its first update while its host Activity is resumed (e.g., a page preloaded next to one without an effect).
@sentry

sentry Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
SDK Size io.sentry.tests.size 8.63.0 (1) release

⚙️ sentry-android Build Distribution Settings

@0xadam-brown 0xadam-brown changed the title fix(android): [Nav3 Lifecycle 3] Stop attributing Nav3 data to screens without a SentryNavEffect fix(android): [Part 3] Stop attributing Nav3 data to screens without a SentryNavEffect Nov 6, 2026
…ts Activity pauses

An Activity pauses before its Fragments, so when a Fragment leaseholder paused with its host, the lease went to a sibling nav root that was about to pause too. With nav transactions restarting on return, that started a navigation transaction as the app was backgrounded or covered (e.g., a DialogFragment with its own SentryNavEffect over a Fragment nav root).
@0xadam-brown
0xadam-brown marked this pull request as ready for review November 6, 2026 12:32
@0xadam-brown
0xadam-brown requested review from a team and adinauer as code owners November 6, 2026 12:32
@0xadam-brown
0xadam-brown requested review from romtsn and runningcode and removed request for a team November 6, 2026 12:32
@0xadam-brown 0xadam-brown added the sanity-check PR needs a lightweight review for obvious issues label Nov 6, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

sanity-check PR needs a lightweight review for obvious issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant