fix: reset AppBar _scrolledUnder flag when scroll context changes#183062
fix: reset AppBar _scrolledUnder flag when scroll context changes#183062ishaquehassan wants to merge 43 commits into
Conversation
|
It looks like this pull request may not have tests. Please make sure to add tests or get an explicit test exemption before merging. If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix? Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. If you believe this PR qualifies for a test exemption, contact "@test-exemption-reviewer" in the #hackers channel in Discord (don't just cc them here, they won't see it!). The test exemption team is a small volunteer group, so all reviewers should feel empowered to ask for tests, without delegating that responsibility entirely to the test exemption group. |
There was a problem hiding this comment.
Code Review
This pull request addresses an issue where the _scrolledUnder flag in _AppBarState was not being reset when the scroll context changed. The change adds logic to didChangeDependencies to check if _scrolledUnder is true and, if so, calls setState to set it to false. This ensures that when the Scaffold body is swapped, the AppBar's appearance correctly reflects the scroll state of the new content.
9780582 to
d8f0730
Compare
|
An existing Git SHA, To re-trigger presubmits after closing or re-opeing a PR, or pushing a HEAD commit (i.e. with |
|
@ishaquehassan Thank you for the great analysis. Before we start a detailed review, it seems that there are a lot of reformating in this PR, which are unnecessary and make the PR really hard to review. Can you undo these lines? (Maybe you used a different auto formatter?) |
da5c188 to
e71f558
Compare
|
Thank you for the review! I've force-pushed the branch to remove all the unnecessary dart format changes. The branch now contains only the 3 relevant commits:
Sorry for the noise from the auto-formatter, I had inadvertently run |
41c10dd to
b5e63fb
Compare
When the Scaffold body is swapped out, didChangeDependencies re-registers the scroll listener on the new context but never resets _scrolledUnder. This causes the AppBar to retain its scrolled-under color even when the new body starts at the top. Reset _scrolledUnder to false in didChangeDependencies after subscribing to the new ScrollNotificationObserver. A ScrollUpdateNotification on the next frame will restore it to true if the new content is also scrolled. Fixes flutter#181701
00d10dd to
95cca1f
Compare
|
The PR now only contains 1 file with 9 lines of addition. It doesn't align with your previous comment, and I also remembered seeing more. Did you lose your changelist during the merge? |
…ll context change
|
oops yeah my bad, i accidentally dropped the test commits when i force pushed to remove the extra formatting. pushed them back, should be good now |
dkwingsmt
left a comment
There was a problem hiding this comment.
Generally looking good!
| // Reset _scrolledUnder when the scroll context is re-established (e.g. | ||
| // when the Scaffold body is swapped out). The new content will update | ||
| // _scrolledUnder via ScrollUpdateNotification on its first scroll event. |
There was a problem hiding this comment.
I rephrased this part of comment, which is clearer to me by explaining "if the new content is already scrolled". What do you think?
| // Reset _scrolledUnder when the scroll context is re-established (e.g. | |
| // when the Scaffold body is swapped out). The new content will update | |
| // _scrolledUnder via ScrollUpdateNotification on its first scroll event. | |
| // Reset _scrolledUnder when the scroll context is re-established (e.g. tab | |
| // switch). Assume a starting state of false; if the new content is already | |
| // scrolled, a ScrollUpdateNotification will be dispatched immediately to | |
| // update the flag. |
There was a problem hiding this comment.
done, went with your suggestion, makes it way clearer
| }); | ||
|
|
||
| group('AppBar _scrolledUnder reset on scroll context change', () { | ||
| testWidgets('scrolledUnder flag resets when Scaffold body is swapped', ( |
There was a problem hiding this comment.
Is it possible to add a test where the new tab has a non-zero scroll, so that the scrollUnder is correctly updated to true immediately?
There was a problem hiding this comment.
added a third test that swaps to a new body, then calls jumpTo(300) on the controller which fires a ScrollUpdateNotification immediately, and verifies elevation goes back to scrolledUnderElevation. works cleanly since jumpTo dispatches the notification synchronously
|
Also: Analyze errors and some other real errors. |
- Rephrase comment per reviewer suggestion: clarify that starting state is assumed false and ScrollUpdateNotification updates it - Add test verifying scrolledUnder correctly becomes true when new body is already at a non-zero scroll position after body swap - Fix dart format issues (collapsed short expressions to single lines)
|
fixed in the latest commit. the analyze failure was dart format complaining about some multi-line expressions that fit on one line. ran dart format, cleaned it up. the other failures (Mac/Windows framework tests, web tests) are all in system_navigator_test and text_field_test, not related to this change |
|
@ishaquehassan FYI recently we've changed the CICD policy in that the presubmit tests will only be triggered with the |
|
Thanks for the heads up on the new CICD workflow @dkwingsmt, good to know! Will ask for the label once the approach is finalized. Still waiting on @Piinks for guidance on the Scaffold-level fix I proposed above. No rush, just want to make sure I build it at the right layer before pushing new code 🙂 |
|
@dkwingsmt hey, would you mind adding the CICD label whenever you get a chance? want to make sure the tests pass with the latest changes 😊 |
|
Merging in main shouldn't have much of an impact on CI. Give me a moment to provide feedback. |
|
In general, you do not need to merge in master so frequently. :) |
This would not be a good approach here. The _notify method being private is WAI. If you were to call that method it would break the API contract. It is meant to trigger notifications related to scrolling, enabling dispatch of notifications for any other reason could cause issues in many other widgets that listen for scrolling changes. |
|
@Piinks totally makes sense, won't touch _notify. And yeah the frequent master merges were just me being curious whether CI would catch anything new with the latest changes, not a workflow I plan to keep doing 😄 So both approaches I tried are dead ends:
At this point I've run out of ideas on where the reset should live. Would you be able to point me toward the right layer or mechanism for handling this? More than happy to implement whatever direction you think is correct 🙂 |
I am not sure how to resolve the issue, unfortunately I am not able to work on it right now. |
No worries, still investigating myself. |
|
@Piinks did a deep dive into the internals and I think I found an approach that actually works. Hear me out on this one 😄 I know I said earlier that Why didChangeDependencies never firedAppBar has no
No signal path from "body changed" to AppBar exists. Fix: add the missing signal pathTrack body identity in scaffold.dart: Add a generation counter to int _bodyGeneration = 0;Detect body changes in @override
void didUpdateWidget(Scaffold oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.body != null && oldWidget.body != null) {
if (!Widget.canUpdate(widget.body!, oldWidget.body!)) {
_bodyGeneration++;
}
} else if ((widget.body == null) != (oldWidget.body == null)) {
_bodyGeneration++;
}
// ... existing FAB/bottomSheet handling
}
Add class _ScaffoldScope extends InheritedWidget {
const _ScaffoldScope({
required this.hasDrawer,
required this.geometryNotifier,
required this.bodyGeneration,
required super.child,
});
final bool hasDrawer;
final _ScaffoldGeometryNotifier geometryNotifier;
final int bodyGeneration;
@override
bool updateShouldNotify(_ScaffoldScope oldWidget) {
return hasDrawer != oldWidget.hasDrawer ||
bodyGeneration != oldWidget.bodyGeneration;
}
}app_bar.dart: Register the dependency using the existing @override
void didChangeDependencies() {
super.didChangeDependencies();
_scrollNotificationObserver?.removeListener(_handleScrollNotification);
// Register dependency on _ScaffoldScope so didChangeDependencies
// fires when the Scaffold body changes (bodyGeneration update).
Scaffold.hasDrawer(context);
final ScaffoldState? scaffoldState = Scaffold.maybeOf(context);
if (scaffoldState != null &&
(scaffoldState.isDrawerOpen || scaffoldState.isEndDrawerOpen)) {
return;
}
_scrollNotificationObserver = ScrollNotificationObserver.maybeOf(context);
_scrollNotificationObserver?.addListener(_handleScrollNotification);
if (_scrolledUnder) {
setState(() {
_scrolledUnder = false;
});
}
}How it handles each case
No synthetic notifications, no new public API, zero changes to the scrolling contract. Just an One thing worth flagging: using Does this direction make sense? |
|
I appreciate the deep dive into the Scaffold internals, but adding a generation counter to such a fundamental widget as a workaround for AppBar state feels like a significant change that could have unintended side effects on performance and rebuild logic for all Scaffold dependents. At this point, since we've cycled through several implementations (from didChangeDependencies to proposed Scaffold modifications), it’s clear the solution isn't straightforward. To ensure we land on an approach that aligns with the framework's design goals and doesn't introduce technical debt, let's move the design discussion back to the original issue #181701. This will allow other maintainers and contributors subscribed to the issue to provide feedback on the best path forward before any more code is written, unfortunately myself and @dkwingsmt cannot go back and forth here trying to solve the issue with you. Once a robust strategy is agreed upon there, we can revisit the implementation here. |
Discovered issues with the implementation.
|
Totally understand @Piinks, moving the discussion to #181701. Really appreciate you and @dkwingsmt taking the time to walk through all these approaches with me, learned a lot about the framework internals through this process 🙌 |
|
Thanks! In the meantime, please stop pushing merges from main to this branch. It is not necessary right now, and creates a lot of noise. 🙏 |
|
I'm converting this PR to a draft since we're planning to discuss the design further. (from triage) |
|
I've marked this PR as not ready to port to flutter/packages yet. |
When the Scaffold body is swapped out (for example, during a tab switch or navigation),
_AppBarState.didChangeDependenciesre-registers the scroll notification listener on the new scroll context but never clears the_scrolledUnderflag. If the previous body had been scrolled down, the AppBar retains its elevated (scrolledUnderElevation) appearance even though the new body starts at the top with nothing scrolled under it.The fix adds a
setState(() { _scrolledUnder = false; })call insidedidChangeDependencies, right after re-subscribing to the new scroll notification observer. The new content will update_scrolledUndercorrectly viaScrollUpdateNotificationon its first scroll event.Two regression tests are added to
app_bar_test.dartcovering both cases: elevation resets after body swap, and elevation is preserved when the body is unchanged.Fixes #181701
Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.
Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the
gemini-code-assistbot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.