Unverified Commit c742c198 authored by Remi Rousselet's avatar Remi Rousselet Committed by GitHub

Add debugDoingBuild flag (#51428)

parent fe0a669c
...@@ -2052,6 +2052,22 @@ abstract class BuildContext { ...@@ -2052,6 +2052,22 @@ abstract class BuildContext {
/// managing the rendering pipeline for this context. /// managing the rendering pipeline for this context.
BuildOwner get owner; BuildOwner get owner;
/// Whether the [widget] is currently updating the widget or render tree.
///
/// For [StatefullWidget]s and [StatelessWidget]s this flag is true while
/// their respective build methods are executing.
/// [RenderObjectWidget]s set this to true while creating or configuring their
/// associated [RenderObject]s.
/// Other [Widget] types may set this to true for conceptually similar phases
/// of their lifecycle.
///
/// When this is true, it is safe for [widget] to establish a dependency to an
/// [InheritedWidget] by calling [dependOnInheritedElement] or
/// [dependOnInheritedWidgetOfExactType].
///
/// Accessing this flag in release mode is not valid.
bool get debugDoingBuild;
/// The current [RenderObject] for the widget. If the widget is a /// The current [RenderObject] for the widget. If the widget is a
/// [RenderObjectWidget], this is the render object that the widget created /// [RenderObjectWidget], this is the render object that the widget created
/// for itself. Otherwise, it is the render object of the first descendant /// for itself. Otherwise, it is the render object of the first descendant
...@@ -4448,6 +4464,10 @@ abstract class ComponentElement extends Element { ...@@ -4448,6 +4464,10 @@ abstract class ComponentElement extends Element {
Element _child; Element _child;
bool _debugDoingBuild = false;
@override
bool get debugDoingBuild => _debugDoingBuild;
@override @override
void mount(Element parent, dynamic newSlot) { void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot); super.mount(parent, newSlot);
...@@ -4475,9 +4495,18 @@ abstract class ComponentElement extends Element { ...@@ -4475,9 +4495,18 @@ abstract class ComponentElement extends Element {
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true)); assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
Widget built; Widget built;
try { try {
assert(() {
_debugDoingBuild = true;
return true;
}());
built = build(); built = build();
assert(() {
_debugDoingBuild = false;
return true;
}());
debugWidgetBuilderValue(widget, built); debugWidgetBuilderValue(widget, built);
} catch (e, stack) { } catch (e, stack) {
_debugDoingBuild = false;
built = ErrorWidget.builder( built = ErrorWidget.builder(
_debugReportException( _debugReportException(
ErrorDescription('building $this'), ErrorDescription('building $this'),
...@@ -5270,6 +5299,10 @@ abstract class RenderObjectElement extends Element { ...@@ -5270,6 +5299,10 @@ abstract class RenderObjectElement extends Element {
RenderObject get renderObject => _renderObject; RenderObject get renderObject => _renderObject;
RenderObject _renderObject; RenderObject _renderObject;
bool _debugDoingBuild = false;
@override
bool get debugDoingBuild => _debugDoingBuild;
RenderObjectElement _ancestorRenderObjectElement; RenderObjectElement _ancestorRenderObjectElement;
RenderObjectElement _findAncestorRenderObjectElement() { RenderObjectElement _findAncestorRenderObjectElement() {
...@@ -5326,7 +5359,15 @@ abstract class RenderObjectElement extends Element { ...@@ -5326,7 +5359,15 @@ abstract class RenderObjectElement extends Element {
@override @override
void mount(Element parent, dynamic newSlot) { void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot); super.mount(parent, newSlot);
assert(() {
_debugDoingBuild = true;
return true;
}());
_renderObject = widget.createRenderObject(this); _renderObject = widget.createRenderObject(this);
assert(() {
_debugDoingBuild = false;
return true;
}());
assert(() { assert(() {
_debugUpdateRenderObjectOwner(); _debugUpdateRenderObjectOwner();
return true; return true;
...@@ -5344,7 +5385,15 @@ abstract class RenderObjectElement extends Element { ...@@ -5344,7 +5385,15 @@ abstract class RenderObjectElement extends Element {
_debugUpdateRenderObjectOwner(); _debugUpdateRenderObjectOwner();
return true; return true;
}()); }());
assert(() {
_debugDoingBuild = true;
return true;
}());
widget.updateRenderObject(this, renderObject); widget.updateRenderObject(this, renderObject);
assert(() {
_debugDoingBuild = false;
return true;
}());
_dirty = false; _dirty = false;
} }
...@@ -5357,7 +5406,15 @@ abstract class RenderObjectElement extends Element { ...@@ -5357,7 +5406,15 @@ abstract class RenderObjectElement extends Element {
@override @override
void performRebuild() { void performRebuild() {
assert(() {
_debugDoingBuild = true;
return true;
}());
widget.updateRenderObject(this, renderObject); widget.updateRenderObject(this, renderObject);
assert(() {
_debugDoingBuild = false;
return true;
}());
_dirty = false; _dirty = false;
} }
......
...@@ -225,6 +225,9 @@ class _TestElement extends Element { ...@@ -225,6 +225,9 @@ class _TestElement extends Element {
void performRebuild() { void performRebuild() {
// Intentionally left empty. // Intentionally left empty.
} }
@override
bool get debugDoingBuild => throw UnimplementedError();
} }
class TestTree extends Object with DiagnosticableTreeMixin { class TestTree extends Object with DiagnosticableTreeMixin {
......
...@@ -1283,6 +1283,150 @@ void main() { ...@@ -1283,6 +1283,150 @@ void main() {
expect(isBuildDecorated, isTrue); expect(isBuildDecorated, isTrue);
expect(isDidChangeDependenciesDecorated, isFalse); expect(isDidChangeDependenciesDecorated, isFalse);
}); });
group('BuildContext.debugDoingbuild', () {
testWidgets('StatelessWidget', (WidgetTester tester) async {
bool debugDoingBuildOnBuild;
await tester.pumpWidget(
StatelessWidgetSpy(
onBuild: (BuildContext context) {
debugDoingBuildOnBuild = context.debugDoingBuild;
},
),
);
final Element context = tester.element(find.byType(StatelessWidgetSpy));
expect(context.debugDoingBuild, isFalse);
expect(debugDoingBuildOnBuild, isTrue);
});
testWidgets('StatefulWidget', (WidgetTester tester) async {
bool debugDoingBuildOnBuild;
bool debugDoingBuildOnInitState;
bool debugDoingBuildOnDidChangeDependencies;
bool debugDoingBuildOnDidUpdateWidget;
bool debugDoingBuildOnDispose;
bool debugDoingBuildOnDeactivate;
await tester.pumpWidget(
Inherited(
0,
child: StatefulWidgetSpy(
onInitState: (BuildContext context) {
debugDoingBuildOnInitState = context.debugDoingBuild;
},
onDidChangeDependencies: (BuildContext context) {
context.dependOnInheritedWidgetOfExactType<Inherited>();
debugDoingBuildOnDidChangeDependencies = context.debugDoingBuild;
},
onBuild: (BuildContext context) {
debugDoingBuildOnBuild = context.debugDoingBuild;
},
),
),
);
final Element context = tester.element(find.byType(StatefulWidgetSpy));
expect(context.debugDoingBuild, isFalse);
expect(debugDoingBuildOnBuild, isTrue);
expect(debugDoingBuildOnInitState, isFalse);
expect(debugDoingBuildOnDidChangeDependencies, isFalse);
await tester.pumpWidget(
Inherited(
1,
child: StatefulWidgetSpy(
onDidUpdateWidget: (BuildContext context) {
debugDoingBuildOnDidUpdateWidget = context.debugDoingBuild;
},
onDidChangeDependencies: (BuildContext context) {
debugDoingBuildOnDidChangeDependencies = context.debugDoingBuild;
},
onBuild: (BuildContext context) {
debugDoingBuildOnBuild = context.debugDoingBuild;
},
onDispose: (BuildContext contex) {
debugDoingBuildOnDispose = context.debugDoingBuild;
},
onDeactivate: (BuildContext contex) {
debugDoingBuildOnDeactivate = context.debugDoingBuild;
},
),
),
);
expect(context.debugDoingBuild, isFalse);
expect(debugDoingBuildOnBuild, isTrue);
expect(debugDoingBuildOnDidUpdateWidget, isFalse);
expect(debugDoingBuildOnDidChangeDependencies, isFalse);
expect(debugDoingBuildOnDeactivate, isNull);
expect(debugDoingBuildOnDispose, isNull);
await tester.pumpWidget(Container());
expect(context.debugDoingBuild, isFalse);
expect(debugDoingBuildOnDispose, isFalse);
expect(debugDoingBuildOnDeactivate, isFalse);
});
testWidgets('RenderObjectWidget', (WidgetTester tester) async {
bool debugDoingBuildOnCreateRenderObject;
bool debugDoingBuildOnUpdateRenderObject;
bool debugDoingBuildOnDidUnmountRenderObject;
final ValueNotifier<int> notifier = ValueNotifier<int>(0);
BuildContext spyContext;
Widget build() {
return ValueListenableBuilder<int>(
valueListenable: notifier,
builder: (BuildContext context, int value, Widget child) {
return Inherited(value, child: child);
},
child: RenderObjectWidgetSpy(
onCreateRenderObjet: (BuildContext context) {
spyContext = context;
context.dependOnInheritedWidgetOfExactType<Inherited>();
debugDoingBuildOnCreateRenderObject = context.debugDoingBuild;
},
onUpdateRenderObject: (BuildContext context) {
debugDoingBuildOnUpdateRenderObject = context.debugDoingBuild;
},
onDidUmountRenderObject: () {
debugDoingBuildOnDidUnmountRenderObject = spyContext.debugDoingBuild;
},
),
);
}
await tester.pumpWidget(build());
spyContext = tester.element(find.byType(RenderObjectWidgetSpy));
expect(spyContext.debugDoingBuild, isFalse);
expect(debugDoingBuildOnCreateRenderObject, isTrue);
expect(debugDoingBuildOnUpdateRenderObject, isNull);
expect(debugDoingBuildOnDidUnmountRenderObject, isNull);
await tester.pumpWidget(build());
expect(spyContext.debugDoingBuild, isFalse);
expect(debugDoingBuildOnUpdateRenderObject, isTrue);
expect(debugDoingBuildOnDidUnmountRenderObject, isNull);
notifier.value++;
debugDoingBuildOnUpdateRenderObject = false;
await tester.pump();
expect(spyContext.debugDoingBuild, isFalse);
expect(debugDoingBuildOnUpdateRenderObject, isTrue);
expect(debugDoingBuildOnDidUnmountRenderObject, isNull);
await tester.pumpWidget(Container());
expect(spyContext.debugDoingBuild, isFalse);
expect(debugDoingBuildOnDidUnmountRenderObject, isFalse);
});
});
} }
class Decorate extends StatefulWidget { class Decorate extends StatefulWidget {
...@@ -1354,6 +1498,9 @@ class NullChildElement extends Element { ...@@ -1354,6 +1498,9 @@ class NullChildElement extends Element {
@override @override
void performRebuild() { } void performRebuild() { }
@override
bool get debugDoingBuild => throw UnimplementedError();
} }
...@@ -1371,6 +1518,9 @@ class DirtyElementWithCustomBuildOwner extends Element { ...@@ -1371,6 +1518,9 @@ class DirtyElementWithCustomBuildOwner extends Element {
@override @override
bool get dirty => true; bool get dirty => true;
@override
bool get debugDoingBuild => throw UnimplementedError();
} }
class Inherited extends InheritedWidget { class Inherited extends InheritedWidget {
...@@ -1474,3 +1624,116 @@ class StatefulElementSpy extends StatefulElement { ...@@ -1474,3 +1624,116 @@ class StatefulElementSpy extends StatefulElement {
super.rebuild(); super.rebuild();
} }
} }
class StatelessWidgetSpy extends StatelessWidget {
const StatelessWidgetSpy({
Key key,
@required this.onBuild,
}) : assert(onBuild != null),
super(key: key);
final void Function(BuildContext) onBuild;
@override
Widget build(BuildContext context) {
onBuild(context);
return Container();
}
}
class StatefulWidgetSpy extends StatefulWidget {
const StatefulWidgetSpy({
Key key,
this.onBuild,
this.onInitState,
this.onDidChangeDependencies,
this.onDispose,
this.onDeactivate,
this.onDidUpdateWidget,
}) : super(key: key);
final void Function(BuildContext) onBuild;
final void Function(BuildContext) onInitState;
final void Function(BuildContext) onDidChangeDependencies;
final void Function(BuildContext) onDispose;
final void Function(BuildContext) onDeactivate;
final void Function(BuildContext) onDidUpdateWidget;
@override
_StatefulWidgetSpyState createState() => _StatefulWidgetSpyState();
}
class _StatefulWidgetSpyState extends State<StatefulWidgetSpy> {
@override
void initState() {
super.initState();
widget.onInitState?.call(context);
}
@override
void deactivate() {
super.deactivate();
widget.onDeactivate?.call(context);
}
@override
void dispose() {
super.dispose();
widget.onDispose?.call(context);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
widget.onDidChangeDependencies?.call(context);
}
@override
void didUpdateWidget(StatefulWidgetSpy oldWidget) {
super.didUpdateWidget(oldWidget);
widget.onDidUpdateWidget?.call(context);
}
@override
Widget build(BuildContext context) {
widget.onBuild?.call(context);
return Container();
}
}
class RenderObjectWidgetSpy extends LeafRenderObjectWidget {
const RenderObjectWidgetSpy({
Key key,
this.onCreateRenderObjet,
this.onUpdateRenderObject,
this.onDidUmountRenderObject,
}) : super(key: key);
final void Function(BuildContext) onCreateRenderObjet;
final void Function(BuildContext) onUpdateRenderObject;
final void Function() onDidUmountRenderObject;
@override
RenderObject createRenderObject(BuildContext context) {
onCreateRenderObjet?.call(context);
return FakeLeafRenderObject();
}
@override
void updateRenderObject(BuildContext context, RenderObject renderObject) {
onUpdateRenderObject?.call(context);
}
@override
void didUnmountRenderObject(RenderObject renderObject) {
super.didUnmountRenderObject(renderObject);
onDidUmountRenderObject?.call();
}
}
class FakeLeafRenderObject extends RenderBox {
@override
void performLayout() {
size = constraints.biggest;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment