Skip to content

Commit 8ce3bfb

Browse files
Remove temporary LayoutBuilder migration flag, defer markNeedsLayout (#149637)
Looks like I did not need this flag since flutter_portal pinned flutter version to 3.10 in their presubmits. I'll create a g3 fix for this since there're only a handful of scuba failures. Also a g3 test revealed a problem with calling `renderObject.markNeedsLayout` directly. It has to be deferred so the render tree is clean during the idle phase and the postFrameCallback phase.
1 parent 180cda3 commit 8ce3bfb

File tree

4 files changed

+58
-17
lines changed

4 files changed

+58
-17
lines changed

packages/flutter/lib/src/widgets/layout_builder.dart

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'package:flutter/foundation.dart';
66
import 'package:flutter/rendering.dart';
7+
import 'package:flutter/scheduler.dart';
78

89
import 'debug.dart';
910
import 'framework.dart';
@@ -81,9 +82,40 @@ class _LayoutBuilderElement<ConstraintType extends Constraints> extends RenderOb
8182
Element? _child;
8283

8384
@override
84-
BuildScope get buildScope => LayoutBuilder.applyDoubleRebuildFix ? _buildScope : super.buildScope;
85+
BuildScope get buildScope => _buildScope;
8586

86-
late final BuildScope _buildScope = BuildScope(scheduleRebuild: renderObject.markNeedsLayout);
87+
late final BuildScope _buildScope = BuildScope(scheduleRebuild: _scheduleRebuild);
88+
89+
// To schedule a rebuild, markNeedsLayout needs to be called on this Element's
90+
// render object (as the rebuilding is done in its performLayout call). However,
91+
// the render tree should typically be kept clean during the postFrameCallbacks
92+
// and the idle phase, so the layout data can be safely read.
93+
bool _deferredCallbackScheduled = false;
94+
void _scheduleRebuild() {
95+
if (_deferredCallbackScheduled) {
96+
return;
97+
}
98+
99+
final bool deferMarkNeedsLayout = switch (SchedulerBinding.instance.schedulerPhase) {
100+
SchedulerPhase.idle || SchedulerPhase.postFrameCallbacks => true,
101+
SchedulerPhase.transientCallbacks || SchedulerPhase.midFrameMicrotasks || SchedulerPhase.persistentCallbacks => false,
102+
};
103+
if (!deferMarkNeedsLayout) {
104+
renderObject.markNeedsLayout();
105+
return;
106+
}
107+
_deferredCallbackScheduled = true;
108+
SchedulerBinding.instance.scheduleFrameCallback(_frameCallback);
109+
}
110+
111+
void _frameCallback(Duration timestamp) {
112+
_deferredCallbackScheduled = false;
113+
// This method is only called when the render tree is stable, if the Element
114+
// is deactivated it will never be reincorporated back to the tree.
115+
if (mounted) {
116+
renderObject.markNeedsLayout();
117+
}
118+
}
87119

88120
@override
89121
void visitChildren(ElementVisitor visitor) {
@@ -284,12 +316,6 @@ class LayoutBuilder extends ConstrainedLayoutBuilder<BoxConstraints> {
284316
required super.builder,
285317
});
286318

287-
/// Temporary flag that controls whether [LayoutBuilder]s and
288-
/// [SliverLayoutBuilder]s should apply the double rebuild fix. This flag is
289-
/// for migration only and **SHOULD NOT BE USED**.
290-
@Deprecated('This is a temporary migration flag. DO NOT USE THIS.') // flutter_ignore: deprecation_syntax (see analyze.dart)
291-
static bool applyDoubleRebuildFix = false;
292-
293319
@override
294320
RenderObject createRenderObject(BuildContext context) => _RenderLayoutBuilder();
295321
}

packages/flutter/test/widgets/framework_test.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ class _MyGlobalObjectKey<T extends State<StatefulWidget>> extends GlobalObjectKe
2323
}
2424

2525
void main() {
26-
setUp(() { LayoutBuilder.applyDoubleRebuildFix = true; });
27-
tearDown(() { LayoutBuilder.applyDoubleRebuildFix = false; });
28-
2926
testWidgets('UniqueKey control test', (WidgetTester tester) async {
3027
final Key key = UniqueKey();
3128
expect(key, hasOneLineDescription);

packages/flutter/test/widgets/layout_builder_and_global_keys_test.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,6 @@ class StatefulWrapperState extends State<StatefulWrapper> {
4141
}
4242

4343
void main() {
44-
setUp(() { LayoutBuilder.applyDoubleRebuildFix = true; });
45-
tearDown(() { LayoutBuilder.applyDoubleRebuildFix = false; });
46-
4744
testWidgets('Moving global key inside a LayoutBuilder', (WidgetTester tester) async {
4845
final GlobalKey<StatefulWrapperState> key = GlobalKey<StatefulWrapperState>();
4946
await tester.pumpWidget(

packages/flutter/test/widgets/layout_builder_test.dart

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ import 'package:flutter/widgets.dart';
77
import 'package:flutter_test/flutter_test.dart';
88

99
void main() {
10-
setUp(() { LayoutBuilder.applyDoubleRebuildFix = true; });
11-
tearDown(() { LayoutBuilder.applyDoubleRebuildFix = false; });
12-
1310
testWidgets('LayoutBuilder parent size', (WidgetTester tester) async {
1411
late Size layoutBuilderSize;
1512
final Key childKey = UniqueKey();
@@ -334,6 +331,30 @@ void main() {
334331
expect(built, 2);
335332
});
336333

334+
testWidgets('LayoutBuilder does not dirty the render tree during the idle phase', (WidgetTester tester) async {
335+
RenderObject? dirtyRenderObject;
336+
void visitSubtree(RenderObject node) {
337+
assert(dirtyRenderObject == null);
338+
if (node.debugNeedsLayout) {
339+
dirtyRenderObject = node;
340+
return;
341+
}
342+
node.visitChildren(visitSubtree);
343+
}
344+
345+
final Widget target = LayoutBuilder(
346+
builder: (BuildContext context, BoxConstraints constraints) => const Placeholder(),
347+
);
348+
await tester.pumpWidget(target);
349+
final RenderObject renderObject = tester.renderObject(find.byWidget(target));
350+
visitSubtree(renderObject);
351+
expect(dirtyRenderObject, isNull);
352+
353+
tester.element(find.byType(Placeholder)).markNeedsBuild();
354+
visitSubtree(renderObject);
355+
expect(dirtyRenderObject, isNull);
356+
});
357+
337358
testWidgets('LayoutBuilder can change size without rebuild', (WidgetTester tester) async {
338359
int built = 0;
339360
final Widget target = LayoutBuilder(

0 commit comments

Comments
 (0)