Add debugDoingBuild flag (#51428)
diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart
index 78ed8e1..5c458aa 100644
--- a/packages/flutter/lib/src/widgets/framework.dart
+++ b/packages/flutter/lib/src/widgets/framework.dart
@@ -2052,6 +2052,22 @@
/// managing the rendering pipeline for this context.
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
/// [RenderObjectWidget], this is the render object that the widget created
/// for itself. Otherwise, it is the render object of the first descendant
@@ -4448,6 +4464,10 @@
Element _child;
+ bool _debugDoingBuild = false;
+ @override
+ bool get debugDoingBuild => _debugDoingBuild;
+
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
@@ -4475,9 +4495,18 @@
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
Widget built;
try {
+ assert(() {
+ _debugDoingBuild = true;
+ return true;
+ }());
built = build();
+ assert(() {
+ _debugDoingBuild = false;
+ return true;
+ }());
debugWidgetBuilderValue(widget, built);
} catch (e, stack) {
+ _debugDoingBuild = false;
built = ErrorWidget.builder(
_debugReportException(
ErrorDescription('building $this'),
@@ -5270,6 +5299,10 @@
RenderObject get renderObject => _renderObject;
RenderObject _renderObject;
+ bool _debugDoingBuild = false;
+ @override
+ bool get debugDoingBuild => _debugDoingBuild;
+
RenderObjectElement _ancestorRenderObjectElement;
RenderObjectElement _findAncestorRenderObjectElement() {
@@ -5326,8 +5359,16 @@
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
+ assert(() {
+ _debugDoingBuild = true;
+ return true;
+ }());
_renderObject = widget.createRenderObject(this);
assert(() {
+ _debugDoingBuild = false;
+ return true;
+ }());
+ assert(() {
_debugUpdateRenderObjectOwner();
return true;
}());
@@ -5344,7 +5385,15 @@
_debugUpdateRenderObjectOwner();
return true;
}());
+ assert(() {
+ _debugDoingBuild = true;
+ return true;
+ }());
widget.updateRenderObject(this, renderObject);
+ assert(() {
+ _debugDoingBuild = false;
+ return true;
+ }());
_dirty = false;
}
@@ -5357,7 +5406,15 @@
@override
void performRebuild() {
+ assert(() {
+ _debugDoingBuild = true;
+ return true;
+ }());
widget.updateRenderObject(this, renderObject);
+ assert(() {
+ _debugDoingBuild = false;
+ return true;
+ }());
_dirty = false;
}
diff --git a/packages/flutter/test/foundation/diagnostics_json_test.dart b/packages/flutter/test/foundation/diagnostics_json_test.dart
index b173f1c..08eaac2 100644
--- a/packages/flutter/test/foundation/diagnostics_json_test.dart
+++ b/packages/flutter/test/foundation/diagnostics_json_test.dart
@@ -225,6 +225,9 @@
void performRebuild() {
// Intentionally left empty.
}
+
+ @override
+ bool get debugDoingBuild => throw UnimplementedError();
}
class TestTree extends Object with DiagnosticableTreeMixin {
diff --git a/packages/flutter/test/widgets/framework_test.dart b/packages/flutter/test/widgets/framework_test.dart
index b2f3bb2..68bb488 100644
--- a/packages/flutter/test/widgets/framework_test.dart
+++ b/packages/flutter/test/widgets/framework_test.dart
@@ -1283,6 +1283,150 @@
expect(isBuildDecorated, isTrue);
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 {
@@ -1354,6 +1498,9 @@
@override
void performRebuild() { }
+
+ @override
+ bool get debugDoingBuild => throw UnimplementedError();
}
@@ -1371,6 +1518,9 @@
@override
bool get dirty => true;
+
+ @override
+ bool get debugDoingBuild => throw UnimplementedError();
}
class Inherited extends InheritedWidget {
@@ -1474,3 +1624,116 @@
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;
+ }
+}