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;
+  }
+}