fix: guard app bar offset listeners against a null view binding - #968
Open
herrerad85 wants to merge 1 commit into
Open
fix: guard app bar offset listeners against a null view binding#968herrerad85 wants to merge 1 commit into
herrerad85 wants to merge 1 commit into
Conversation
These layouts set scroll flags including snap, so releasing a partly scrolled collapsing app bar starts BaseBehavior's offset ValueAnimator to settle it to the nearest snap position. That animator is ticked by the thread's AnimationHandler rather than by the view hierarchy, so detaching the fragment view does not cancel it, and the listener is never unregistered. Navigate away while it is still settling and it keeps calling onOffsetChanged after onDestroyView has nulled bind, which throws. Twelve fragments register such a listener and every one of them reads bind inside it. Each gets the early return already used elsewhere in this code base. Reproduced on a Galaxy S25 Ultra running Android 16, on a debug build. It needs a drag release rather than a fling, because a fling runs the bar to a rest position and finishes the animator before you can navigate away. Same scripted gesture, same device, same session: the unmodified build died on 4 of 5 attempts, this branch survived 6 of 6. PlaylistPageFragment's landscape scroll sync read bind.playlistInfoScrollView inside a RecyclerView scroll listener. It now holds the view in a local, which is what AlbumPageFragment already does for the same block. Checked in landscape that the info pane still scrolls with the list. DirectoryFragment, IndexFragment and SongListPageFragment wrapped statements in initAppBar in a bind != null check. Each of those methods dereferences bind unguarded on its first line, so those checks could never be false. Removed.
Contributor
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
What this fixes
Issue #966. A
NullPointerExceptionthrown from anAppBarLayoutoffset listener on a screen the user has already left. The pattern dates to 2021 and predates this fork.Why it happens
All twelve of these layouts carry
snapin their scroll flags. Releasing a partly scrolled app bar startsBaseBehavior's offsetValueAnimatorto settle it to the nearest snap position.That animator is ticked by the thread's
AnimationHandlerrather than by the view hierarchy, so detaching the view does not cancel it, andremoveOnOffsetChangedListeneris never called anywhere in the app, so the listener stays registered as well. Navigate away while the bar is still settling and it keeps callingonOffsetChangedafteronDestroyViewhas nulledbind, which throws.One correction to the analysis in the issue: it is not a layout pass driving the callback. Deobfuscated stack from a debug build, trimmed:
The
AppBarLayoutreportedisAttachedToWindow()as false at every post teardown call.Reproducing it
It needs a drag release rather than a fling. A fling runs the bar to a rest position and finishes the animator before you can navigate away, which is why this is easy to miss.
What the change does
Twelve fragments register an offset listener and every one of them reads
bindinside it. Each gets the early return already used elsewhere in this code base.PlaylistPageFragment's landscape scroll sync readbind.playlistInfoScrollViewinside aRecyclerViewscroll listener. It now holds the view in a local, which is whatAlbumPageFragmentalready does for the same block.DirectoryFragment,IndexFragmentandSongListPageFragmentwrapped statements ininitAppBarin fivebind != nullchecks that can never be false. Each of those methods dereferencesbindunguarded on its first line, so a nullbindwould already have thrown before reaching them. Removed, which also makes those three match the other nine.What it does not do
It does not unregister the listener. The animator keeps ticking and the callback still fires after the view is gone, it just returns early instead of throwing. Unregistering would mean holding a listener reference as a field in twelve fragments, which is a wider change than this crash calls for.
Verification
Galaxy S25 Ultra on Android 16, debug builds of the same commit with and without the change, same scripted gesture, same device, same session.
IndexFragmentseparately survived 5 of 5 with the animator duration scale raised to 10, which widens the window the crash needs.The landscape scroll sync was checked on a playlist in landscape: the info pane still scrolls with the list, across four back navigations and three rotations taken mid scroll.