diff --git a/packages/flutter/lib/src/widgets/binding.dart b/packages/flutter/lib/src/widgets/binding.dart index e320810b67..66d9efaab7 100644 --- a/packages/flutter/lib/src/widgets/binding.dart +++ b/packages/flutter/lib/src/widgets/binding.dart @@ -474,6 +474,15 @@ abstract class WidgetsBinding extends BindingBase with SchedulerBinding, Gesture int _deferFirstFrameReportCount = 0; bool get _reportFirstFrame => _deferFirstFrameReportCount == 0; + /// Whether the first frame has finished rendering. + /// + /// Only valid in profile and debug builds, it can't be used in release + /// builds. + /// It can be deferred using [deferFirstFrameReport] and + /// [allowFirstFrameReport]. + /// The value is set at the end of the call to [drawFrame]. + bool get debugDidSendFirstFrameEvent => !_needToReportFirstFrame; + /// Tell the framework not to report the frame it is building as a "useful" /// first frame until there is a corresponding call to [allowFirstFrameReport]. /// diff --git a/packages/flutter/lib/src/widgets/widget_inspector.dart b/packages/flutter/lib/src/widgets/widget_inspector.dart index 69478abc7c..c400ea06d8 100644 --- a/packages/flutter/lib/src/widgets/widget_inspector.dart +++ b/packages/flutter/lib/src/widgets/widget_inspector.dart @@ -12,6 +12,7 @@ import 'dart:ui' show Offset; import 'package:flutter/foundation.dart'; import 'package:flutter/painting.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; import 'basic.dart'; import 'binding.dart'; @@ -200,6 +201,13 @@ class WidgetInspectorService { return id; } + /// Returns whether the application has rendered its first frame and it is + /// appropriate to display the Widget tree in the inspector. + bool isWidgetTreeReady([String groupName]) { + return WidgetsBinding.instance != null && + WidgetsBinding.instance.debugDidSendFirstFrameEvent; + } + /// Returns the Dart object associated with a reference id. /// /// The `groupName` parameter is not required by is added to regularize the @@ -210,8 +218,9 @@ class WidgetInspectorService { return null; final _InspectorReferenceData data = _idToReferenceData[id]; - if (data == null) - throw new FlutterError('Id does not exist'); + if (data == null) { + throw new FlutterError('Id does not exist.'); + } return data.object; } @@ -281,7 +290,16 @@ class WidgetInspectorService { selection.current = object; } if (selectionChangedCallback != null) { - selectionChangedCallback(); + if (WidgetsBinding.instance.schedulerPhase == SchedulerPhase.idle) { + selectionChangedCallback(); + } else { + // It isn't safe to trigger the selection change callback if we are in + // the middle of rendering the frame. + SchedulerBinding.instance.scheduleTask( + selectionChangedCallback, + Priority.touch, + ); + } } return true; } @@ -467,15 +485,27 @@ class _WidgetInspectorState extends State /// as selecting the edge of the bounding box. static const double _kEdgeHitMargin = 2.0; + InspectorSelectionChangedCallback _selectionChangedCallback; @override void initState() { super.initState(); - WidgetInspectorService.instance.selectionChangedCallback = () { + + _selectionChangedCallback = () { setState(() { // The [selection] property which the build method depends on has // changed. }); }; + assert(WidgetInspectorService.instance.selectionChangedCallback == null); + WidgetInspectorService.instance.selectionChangedCallback = _selectionChangedCallback; + } + + @override + void dispose() { + if (WidgetInspectorService.instance.selectionChangedCallback == _selectionChangedCallback) { + WidgetInspectorService.instance.selectionChangedCallback = null; + } + super.dispose(); } bool _hitTestHelper(