Fix Sentry JETPACK-ANDROID-1G8W: guard ReaderPostPagerActivity adapter restore - #22856
Merged
nbradbury merged 10 commits intoMay 13, 2026
Merged
Conversation
…r restore
`ReaderPostPagerActivity.loadPosts` runs a background thread and hops to
the main thread to assign `viewPager.adapter = PostPagerAdapter(...)`.
After a process-death restore, `FragmentStateAdapter` tries to rehydrate
saved-state fragment keys and throws `IllegalStateException("Fragment no
longer exists for key f#0: unique id …")` when the IDs no longer match
the freshly-built adapter, crashing the activity.
Wrap the adapter assignment and follow-on `setCurrentItem` /
`trackPostAtPositionIfNeeded` calls in a try/catch for
`IllegalStateException`, log the failure, and leave the user on the
previous screen instead of bringing the activity down. Also add an
`isDestroyed` check alongside `isFinishing` so we drop the work if the
activity has already been torn down between the worker thread and the
UI hop.
Sentry: JETPACK-ANDROID-1G8W
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Collaborator
Generated by 🚫 Danger |
Contributor
|
|
Contributor
|
|
Address the FragmentStateAdapter rehydration crash at the root: bumping pagerGeneration after a saved-state restore makes containsItem() reject the stale FSA fragment keys, so FSA never tries to resurrect fragments that no longer exist. The remaining try/catch is narrowed to the actual adapter-assignment line and now finish()es the activity rather than leaving the user on a blank pager. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match the Android framework's vocabulary (onRestoreInstanceState, FragmentStateAdapter.restoreState). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
androidx.viewpager2:viewpager2:1.1.0's FragmentStateAdapter.restoreState iterates over saved f# keys and unconditionally calls FragmentManager.getFragment(bundle, key) without consulting containsItem(). After process recreation those who-UUIDs no longer resolve in the rebuilt FragmentManager, so the call throws IllegalStateException — which we caught and finish()ed, silently kicking the user back to the post list whenever the activity was recreated. Install a non-StatefulAdapter placeholder in onPostCreate so ViewPager2.restorePendingState() drops its mPendingAdapterState before loadPosts() attaches the real PostPagerAdapter. blogId / postId restore in onSaveInstanceState still puts the user back on the same post. Also tighten hasPagerAdapter() to test for PostPagerAdapter so the placeholder doesn't masquerade as a loaded adapter, and revert the pagerGeneration+1 bump (it only guarded s# keys, which never get a chance to fire now that FSA never sees the saved bundle). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
onPostCreate's relative ordering vs. view-hierarchy state restoration isn't reliable across Android versions, so the placeholder adapter we installed there sometimes ran while mPendingAdapterState was still null — leaving the real ISE still in play on restore. Move the assignment directly into onRestoreInstanceState, after super has populated mPendingAdapterState. Also add a debug log so the path is visible in logcat for verification. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Logcat from a "Don't keep activities" restore shows the placeholder adapter installs correctly (no ISE) but the activity pauses 6ms after onResume — something else is tearing it down. Add diagnostics on onPause / onStop / onDestroy / finish() / onNewIntent (with a stack trace on finish) so we can pinpoint the actual finish source. To be removed once the cause is identified. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diagnostics on onCreate (with intent action/data), onStart, onResume (with hasPagerAdapter/backFromLogin), and the deep-link branch entry, so we can correlate against onPause/onStop/onDestroy and tell whether the system is destroying the activity through a code path or simply mid-lifecycle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restore ReaderPostPagerActivity.kt to its post-fix state from 673135c now that the cause of the post-restore "returns to post list" symptom has been identified (WPLaunchActivity's CLEAR_TASK launch path, fixed in the next commit). Keeps the FSA fix and the "dropping pending FSA state" log; drops the rest. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the user taps the launcher icon after their task was destroyed (process death or "Don't keep activities"), Android brings the existing task forward and starts WPLaunchActivity on top of the restored stack — e.g. a ReaderPostPagerActivity the user had open. WPLaunchActivity then launches WPMainActivity with FLAG_ACTIVITY_CLEAR_TASK, wiping the restored screen and leaving the user back on the home tab. Detect this case via isTaskRoot — if we're not the root, the task already has activities to restore, so we just finish() and let Android bring the existing stack forward unchanged. The CLEAR_TASK launch path still runs as before for genuinely cold starts (where WPLaunchActivity is the task root). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nbradbury
marked this pull request as ready for review
May 12, 2026 21:08
nbradbury
enabled auto-merge (squash)
May 13, 2026 11:02
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Fixes JETPACK-ANDROID-1G8W.
Summary
Two issues, both surfaced when
ReaderPostPagerActivityis recreated after process death or with "Don't keep activities" enabled.1.
Fragment no longer exists for key f#…crash (the Sentry issue)ViewPager2's adapter iterates the saved
f#(fragment-reference) keys and unconditionally callsFragmentManager.getFragment(bundle, key)— it does not consultcontainsItem()for those keys. After process recreation thosewho-UUIDs no longer resolve in the rebuiltFragmentManager, so the call throws:This is the top Jetpack-Android crash by user count after the recent merges (Sentry: 92 distinct users in the last 30 days, Play Console: 36 distinct users for the same stack).
2. Pre-existing UX bug: launcher icon wipes the restored stack
Surfaced while testing the fix for #1 with "Don't keep activities" enabled: tapping the launcher icon after the activity stack was destroyed always returned the user to the home tab. Cause:
WPLaunchActivityis the LAUNCHER target, and on every cold launch it restartsWPMainActivitywithFLAG_ACTIVITY_CLEAR_TASK, wiping any restored stack underneath it.Fixes
onRestoreInstanceStateand assign a non-StatefulAdapterplaceholder toViewPager2. That makesrestorePendingState()discardmPendingAdapterStatewithout invoking the real adapter'srestoreState(), so the stalef#references never get resolved.hasPagerAdapter()to testviewPager.adapter is PostPagerAdapter, so the placeholder doesn't masquerade as a loaded adapter and causeonResume()to skiploadPosts().isDestroyedalongside the existingisFinishingearly-out so we also drop the UI work if the activity was torn down between the worker thread and the UI hop.try/catch (IllegalStateException)aroundviewPager.adapter = adapteras defense in depth: on failure, log viaAppLog.eandfinish()so the user returns to the previous screen instead of being stranded on a blank pager.Test plan