Skip to content

Commit a624cb7

Browse files
authored
Keep dirty manipulations private to Element base class (#109401)
1 parent 28de3d4 commit a624cb7

File tree

6 files changed

+95
-38
lines changed

6 files changed

+95
-38
lines changed

packages/flutter/lib/src/cupertino/text_selection_toolbar.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,9 +1016,6 @@ class _NullElement extends Element {
10161016

10171017
@override
10181018
bool get debugDoingBuild => throw UnimplementedError();
1019-
1020-
@override
1021-
void performRebuild() { }
10221019
}
10231020

10241021
class _NullWidget extends Widget {

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

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4507,6 +4507,10 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
45074507
}
45084508

45094509
/// Returns true if the element has been marked as needing rebuilding.
4510+
///
4511+
/// The flag is true when the element is first created and after
4512+
/// [markNeedsBuild] has been called. The flag is reset to false in the
4513+
/// [performRebuild] implementation.
45104514
bool get dirty => _dirty;
45114515
bool _dirty = true;
45124516

@@ -4580,10 +4584,14 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
45804584
/// Called by the [BuildOwner] when [BuildOwner.scheduleBuildFor] has been
45814585
/// called to mark this element dirty, by [mount] when the element is first
45824586
/// built, and by [update] when the widget has changed.
4587+
///
4588+
/// The method will only rebuild if [dirty] is true. To rebuild irregardless
4589+
/// of the [dirty] flag, set `force` to true. Forcing a rebuild is convenient
4590+
/// from [update], during which [dirty] is false.
45834591
@pragma('vm:prefer-inline')
4584-
void rebuild() {
4592+
void rebuild({bool force = false}) {
45854593
assert(_lifecycleState != _ElementLifecycle.initial);
4586-
if (_lifecycleState != _ElementLifecycle.active || !_dirty) {
4594+
if (_lifecycleState != _ElementLifecycle.active || (!_dirty && !force)) {
45874595
return;
45884596
}
45894597
assert(() {
@@ -4618,8 +4626,13 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
46184626
/// Cause the widget to update itself.
46194627
///
46204628
/// Called by [rebuild] after the appropriate checks have been made.
4629+
///
4630+
/// The base implementation only clears the [dirty] flag.
46214631
@protected
4622-
void performRebuild();
4632+
@mustCallSuper
4633+
void performRebuild() {
4634+
_dirty = false;
4635+
}
46234636
}
46244637

46254638
class _ElementDiagnosticableTreeNode extends DiagnosticableTreeNode {
@@ -4901,7 +4914,7 @@ abstract class ComponentElement extends Element {
49014914
} finally {
49024915
// We delay marking the element as clean until after calling build() so
49034916
// that attempts to markNeedsBuild() during build() will be ignored.
4904-
_dirty = false;
4917+
super.performRebuild(); // clears the "dirty" flag
49054918
}
49064919
try {
49074920
_child = updateChild(_child, built, slot);
@@ -4955,8 +4968,7 @@ class StatelessElement extends ComponentElement {
49554968
void update(StatelessWidget newWidget) {
49564969
super.update(newWidget);
49574970
assert(widget == newWidget);
4958-
_dirty = true;
4959-
rebuild();
4971+
rebuild(force: true);
49604972
}
49614973
}
49624974

@@ -5053,10 +5065,6 @@ class StatefulElement extends ComponentElement {
50535065
super.update(newWidget);
50545066
assert(widget == newWidget);
50555067
final StatefulWidget oldWidget = state._widget!;
5056-
// We mark ourselves as dirty before calling didUpdateWidget to
5057-
// let authors call setState from within didUpdateWidget without triggering
5058-
// asserts.
5059-
_dirty = true;
50605068
state._widget = widget as StatefulWidget;
50615069
final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
50625070
assert(() {
@@ -5072,7 +5080,7 @@ class StatefulElement extends ComponentElement {
50725080
}
50735081
return true;
50745082
}());
5075-
rebuild();
5083+
rebuild(force: true);
50765084
}
50775085

50785086
@override
@@ -5217,8 +5225,7 @@ abstract class ProxyElement extends ComponentElement {
52175225
super.update(newWidget);
52185226
assert(widget == newWidget);
52195227
updated(oldWidget);
5220-
_dirty = true;
5221-
rebuild();
5228+
rebuild(force: true);
52225229
}
52235230

52245231
/// Called during build when the [widget] has changed.
@@ -5746,7 +5753,7 @@ abstract class RenderObjectElement extends Element {
57465753
}());
57475754
assert(_slot == newSlot);
57485755
attachRenderObject(newSlot);
5749-
_dirty = false;
5756+
super.performRebuild(); // clears the "dirty" flag
57505757
}
57515758

57525759
@override
@@ -5768,7 +5775,7 @@ abstract class RenderObjectElement extends Element {
57685775
}
57695776

57705777
@override
5771-
void performRebuild() {
5778+
void performRebuild() { // ignore: must_call_super, _performRebuild calls super.
57725779
_performRebuild(); // calls widget.updateRenderObject()
57735780
}
57745781

@@ -5783,7 +5790,7 @@ abstract class RenderObjectElement extends Element {
57835790
_debugDoingBuild = false;
57845791
return true;
57855792
}());
5786-
_dirty = false;
5793+
super.performRebuild(); // clears the "dirty" flag
57875794
}
57885795

57895796
/// Updates the children of this element to use new widgets.
@@ -6539,9 +6546,6 @@ class _NullElement extends Element {
65396546

65406547
@override
65416548
bool get debugDoingBuild => throw UnimplementedError();
6542-
6543-
@override
6544-
void performRebuild() => throw UnimplementedError();
65456549
}
65466550

65476551
class _NullWidget extends Widget {

packages/flutter/test/cupertino/colors_test.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -591,9 +591,6 @@ class _NullElement extends Element {
591591

592592
@override
593593
bool get debugDoingBuild => throw UnimplementedError();
594-
595-
@override
596-
void performRebuild() { }
597594
}
598595

599596
class _NullWidget extends Widget {

packages/flutter/test/foundation/diagnostics_json_test.dart

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,6 @@ void main() {
220220
class _TestElement extends Element {
221221
_TestElement() : super(const Placeholder());
222222

223-
@override
224-
void performRebuild() {
225-
// Intentionally left empty.
226-
}
227-
228223
@override
229224
bool get debugDoingBuild => throw UnimplementedError();
230225
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/widgets.dart';
6+
import 'package:flutter_test/flutter_test.dart';
7+
8+
void main() {
9+
testWidgets('Can call setState from didUpdateWidget', (WidgetTester tester) async {
10+
await tester.pumpWidget(const Directionality(
11+
textDirection: TextDirection.ltr,
12+
child: WidgetUnderTest(text: 'hello'),
13+
));
14+
15+
expect(find.text('hello'), findsOneWidget);
16+
expect(find.text('world'), findsNothing);
17+
final _WidgetUnderTestState state = tester.state<_WidgetUnderTestState>(find.byType(WidgetUnderTest));
18+
expect(state.setStateCalled, 0);
19+
expect(state.didUpdateWidgetCalled, 0);
20+
21+
await tester.pumpWidget(const Directionality(
22+
textDirection: TextDirection.ltr,
23+
child: WidgetUnderTest(text: 'world'),
24+
));
25+
26+
expect(find.text('world'), findsOneWidget);
27+
expect(find.text('hello'), findsNothing);
28+
expect(state.setStateCalled, 1);
29+
expect(state.didUpdateWidgetCalled, 1);
30+
});
31+
}
32+
33+
class WidgetUnderTest extends StatefulWidget {
34+
const WidgetUnderTest({super.key, required this.text});
35+
36+
final String text;
37+
38+
@override
39+
State<WidgetUnderTest> createState() => _WidgetUnderTestState();
40+
}
41+
42+
class _WidgetUnderTestState extends State<WidgetUnderTest> {
43+
late String text = widget.text;
44+
45+
int setStateCalled = 0;
46+
int didUpdateWidgetCalled = 0;
47+
48+
@override
49+
void didUpdateWidget(WidgetUnderTest oldWidget) {
50+
super.didUpdateWidget(oldWidget);
51+
didUpdateWidgetCalled += 1;
52+
if (oldWidget.text != widget.text) {
53+
// This setState is load bearing for the test.
54+
setState(() {
55+
text = widget.text;
56+
});
57+
}
58+
}
59+
60+
@override
61+
void setState(VoidCallback fn) {
62+
super.setState(fn);
63+
setStateCalled += 1;
64+
}
65+
66+
@override
67+
Widget build(BuildContext context) {
68+
return Text(text);
69+
}
70+
}

packages/flutter/test/widgets/framework_test.dart

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1861,9 +1861,6 @@ class DirtyElementWithCustomBuildOwner extends Element {
18611861

18621862
final BuildOwner _owner;
18631863

1864-
@override
1865-
void performRebuild() {}
1866-
18671864
@override
18681865
BuildOwner get owner => _owner;
18691866

@@ -1968,9 +1965,9 @@ class StatefulElementSpy extends StatefulElement {
19681965
_Stateful get _statefulWidget => widget as _Stateful;
19691966

19701967
@override
1971-
void rebuild() {
1968+
void rebuild({bool force = false}) {
19721969
_statefulWidget.onElementRebuild?.call(this);
1973-
super.rebuild();
1970+
super.rebuild(force: force);
19741971
}
19751972
}
19761973

@@ -2115,9 +2112,6 @@ class _EmptyElement extends Element {
21152112

21162113
@override
21172114
bool get debugDoingBuild => false;
2118-
2119-
@override
2120-
void performRebuild() {}
21212115
}
21222116

21232117
class _TestLeaderLayerWidget extends SingleChildRenderObjectWidget {

0 commit comments

Comments
 (0)