-
Notifications
You must be signed in to change notification settings - Fork 28.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make sure LayoutBuilder
rebuild in an inactive route
#154681
base: master
Are you sure you want to change the base?
Make sure LayoutBuilder
rebuild in an inactive route
#154681
Conversation
f264ff6
to
1adbbda
Compare
This comment was marked as off-topic.
This comment was marked as off-topic.
// This ensures that the layout callback will be run even if an ancestor | ||
// chooses to not lay out this subtree (for example, OverlayEntries with | ||
// `maintainState` set to true). | ||
owner?._nodesNeedingLayout.add(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For my own understanding: Wouldn't this case child nodes to potentially also get layed out unnecessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or is the idea that since the contrains haven't changed, the layout builder is pretty much guaranteed to pass the same constraints to its child and layout would short-circuit right there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah the render children may also do layout now which can be unnecessary. But I think (at least for RenderTheater
) if you rebuild a widget subtree in an inactive maintainState
OverlayEntry, relayout boundaries in the render subtree are still operational and call performLayout
as usual?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the reason that we don't want to find the closest boundary because we are not sure whether between that and here may skips the layout?
Is it possible to short circuit the layout pass and directly marksNeedsBuild of the subtree if this is in active route? I am a little concern about removing the check that layout can start at non layout boundary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to short circuit the layout pass and directly marksNeedsBuild of the subtree if this is in active route? I am a little concern about removing the check that layout can start at non layout boundary.
I am not aware of any other ways to detect whether a LayoutBuilder
s layout has been skipped, other than adding its node to the dirtly layout list.
If the parent skips layout, then it doesn't care about the size of this render object so this render object is effectively a relayout boundary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I can't think of a better way to do this. especially we only want to rebuild during layout phase .
// The initial value of this flag must be set to true to prevent the layout | ||
// callback from being called when the tree has never been laid out. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand this comment: _needsRebuild doesn't seem to be used as a guard for runLayoutCallback. And also, why do we not want to run the callback during the first time the tree is laid out?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This RenderObject will be added to the PipelineOwner's dirty list, but if its ancestors were never laid out we can't run the layout callback because the constraints are not known yet. Changed the class documentation to
/// A mixin for [RenderObject] subclasses with a layout callback. The mixin
/// guarantees the layout callback will be called even if this [RenderObject]
/// skips doing layout, unless the [RenderObject] has never been laid out and
/// does not have valid [constraints].
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This new explanation sounds good to me, but you may have forgotten to push the change up to github.
@@ -850,6 +850,52 @@ void main() { | |||
expect(paintCount, 3); | |||
expect(mostRecentOffset, const Offset(60, 60)); | |||
}); | |||
|
|||
testWidgets('LayoutBuilder in a subtree that skips layout does not rebuild during the initial treewalk', (WidgetTester tester) async { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain why this is?
Fixes #154060 The error message doesn't make sense to me since one can call `setState` during the idle phase, and I'm not sure what this is guarding against without the right error message. In the case of #154060 the layout builder was never laid out: ``` ��child 1: _RenderLayoutBuilder#7c319 NEEDS-LAYOUT NEEDS-PAINT � creator: LayoutBuilder � _BodyBuilder � MediaQuery � � LayoutId-[<_ScaffoldSlot.body>] � CustomMultiChildLayout � � _ActionsScope � Actions � AnimatedBuilder � DefaultTextStyle � � AnimatedDefaultTextStyle � _InkFeatures-[GlobalKey#1f6eb ink � renderer] � NotificationListener<LayoutChangedNotification> � � � parentData: offset=Offset(0.0, 0.0); id=_ScaffoldSlot.body � constraints: MISSING � size: MISSING ``` So #154681 doesn't really fix #154060 since the layout callback cannot be run without a set of valid constraints. Before the `BuildScope` change all `_inDirtyList` flags were unset after the `BuildOwner` finishes rebuilding the widget tree, so `LayoutBuilder._inDirtyLst` is always set to false after a frame even for layout builders that were never laid out. With the `BuildScope` change, `LayoutBuilder` has its own `BuildScope` which is only flushed after LayoutBuilder gets its constraints.
(PR Triage): FYI there are some merge conflicts here. :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@@ -2446,7 +2446,6 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge | |||
@pragma('vm:notify-debugger-on-exception') | |||
void _layoutWithoutResize() { | |||
assert(_needsLayout); | |||
assert(_relayoutBoundary == this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of remove this, can it add a || this is RenderObjectWithLayoutCallbackMixin
// This ensures that the layout callback will be run even if an ancestor | ||
// chooses to not lay out this subtree (for example, OverlayEntries with | ||
// `maintainState` set to true). | ||
owner?._nodesNeedingLayout.add(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I can't think of a better way to do this. especially we only want to rebuild during layout phase .
(Flyby comment from triage) This has some merge conflicts now. |
@LongCatIsLooong said they will come back to this pr once they finished Full keyboard access pr |
b3f906c
to
5786841
Compare
(requested another review since I remember I spoke with @goderbauer about the issue). |
This pull request executed golden file tests, but it has not been updated in a while (20+ days). Test results from Gold expire after as many days, so this pull request will need to be updated with a fresh commit in order to get results from Gold. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
(Triage) Hey @LongCatIsLooong, is this change still something you'd like to return to? |
Yeah I'm planning to get to this PR tomorrow |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM other than the updates and test fixes that this PR needs.
@@ -4108,6 +4107,64 @@ mixin RenderObjectWithChildMixin<ChildType extends RenderObject> on RenderObject | |||
} | |||
} | |||
|
|||
/// A mixin for [RenderObject] subclasses with a layout callback. The mixin |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Break into a new paragraph after the first sentence here.
/// Informs the framework that the layout callback has been updated and must | ||
/// be invoked again. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Consider explaining the difference between this and markNeedsLayout here. Also maybe add a "see also" for this method in the docs of markNeedsLayout?
Prompted by #154060: widgets should always rebuild even when off-screen. The ancestor widget could be trying to pass down information that is not related to the UI state, or trying to pause video playback. Widgets with global keys should also always rebuild to make sure the widget tree is consistent in terms of global keys.
Also prevents unnecessary repaints: #106306 (comment)This works by adding
_RenderLayoutBuilder
as a relayout boundary in the dirtly layout list so the layout callback always gets a chance to run if marked dirty.Pre-launch Checklist
///
).If you need help, consider asking for advice on the #hackers-new channel on Discord.