diff --git a/packages/flutter/lib/src/widgets/dismissable.dart b/packages/flutter/lib/src/widgets/dismissable.dart index 05158c551f..08058a43cd 100644 --- a/packages/flutter/lib/src/widgets/dismissable.dart +++ b/packages/flutter/lib/src/widgets/dismissable.dart @@ -256,7 +256,7 @@ class _DismissableState extends State { assert(_resizeAnimation.status == AnimationStatus.completed); throw new WidgetError( 'Dismissable widget completed its resize animation without being removed from the tree.\n' - 'Make sure to implement the onDismissed handler and to immediately remove the Dismissable\n' + 'Make sure to implement the onDismissed handler and to immediately remove the Dismissable ' 'widget from the application once that handler has fired.' ); } diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 1c65f8942f..d085a24b8a 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -149,8 +149,12 @@ abstract class GlobalKey> extends Key { message += 'The following GlobalKey was found multiple times among mounted elements: $key (${_debugDuplicates[key]} instances)\n'; message += 'The most recently registered instance is: ${_registry[key]}\n'; } - if (!_debugDuplicates.isEmpty) - throw new WidgetError('Incorrect GlobalKey usage.', message); + if (!_debugDuplicates.isEmpty) { + throw new WidgetError( + 'Incorrect GlobalKey usage.\n' + '$message' + ); + } return true; } @@ -1109,9 +1113,10 @@ abstract class BuildableElement extends Element { } if (_debugStateLocked && (!_debugAllowIgnoredCallsToMarkNeedsBuild || !dirty)) { throw new WidgetError( - 'Cannot mark this component as needing to build because the framework is ' - 'already in the process of building widgets. A widget can be marked as ' - 'needing to be built during the build phase only if one if its ancestor ' + 'setState() or markNeedsBuild() called during build.\n' + 'This component cannot be marked as needing to build because the framework ' + 'is already in the process of building widgets. A widget can be marked as ' + 'needing to be built during the build phase only if one if its ancestors ' 'is currently building. This exception is allowed because the framework ' 'builds parent widgets before children, which means a dirty descendant ' 'will always be built. Otherwise, the framework might not visit this ' @@ -1208,8 +1213,11 @@ abstract class ComponentElement extends BuildableElement { assert(() { if (built == null) { throw new WidgetError( - 'A build function returned null. Build functions must never return null.', - 'The offending widget is: $widget' + 'A build function returned null.\n' + 'The offending widget is: $widget\n' + 'Build functions must never return null. ' + 'To return an empty space that causes the building widget to fill available room, return "new Container()". ' + 'To return an empty space that takes as little room as possible, return "new Container(width: 0.0, height: 0.0)".' ); } return true; @@ -1289,7 +1297,11 @@ class StatefulComponentElement> assert(() { if (_state._debugLifecycleState == _StateLifecycle.initialized) return true; - throw new WidgetError('${_state.runtimeType}.initState failed to call super.initState.'); + throw new WidgetError( + '${_state.runtimeType}.initState failed to call super.initState.\n' + 'initState() implementations must always call their superclass initState() method, to ensure ' + 'that the entire widget is initialized correctly.' + ); }); assert(() { _state._debugLifecycleState = _StateLifecycle.ready; return true; }); super._firstBuild(); @@ -1324,7 +1336,11 @@ class StatefulComponentElement> assert(() { if (_state._debugLifecycleState == _StateLifecycle.defunct) return true; - throw new WidgetError('${_state.runtimeType}.dispose failed to call super.dispose.'); + throw new WidgetError( + '${_state.runtimeType}.dispose failed to call super.dispose.\n' + 'dispose() implementations must always call their superclass dispose() method, to ensure ' + 'that all the resources used by the widget are fully released.' + ); }); assert(!dirty); // See BuildableElement.unmount for why this is important. _state._element = null; @@ -1381,12 +1397,15 @@ class ParentDataElement extends _ProxyElement { } if (ancestor != null && badAncestors.isEmpty) return true; - throw new WidgetError('Incorrect use of ParentDataWidget.', widget.debugDescribeInvalidAncestorChain( - description: "$this", - ownershipChain: parent.debugGetOwnershipChain(10), - foundValidAncestor: ancestor != null, - badAncestors: badAncestors - )); + throw new WidgetError( + 'Incorrect use of ParentDataWidget.\n' + + widget.debugDescribeInvalidAncestorChain( + description: "$this", + ownershipChain: parent.debugGetOwnershipChain(10), + foundValidAncestor: ancestor != null, + badAncestors: badAncestors + ) + ); }); super.mount(parent, slot); } @@ -1507,7 +1526,14 @@ abstract class RenderObjectElement extends Buildab // 'BuildContext' argument which you can pass to Theme.of() and other // InheritedWidget APIs which eventually trigger a rebuild.) assert(() { - throw new WidgetError('$runtimeType failed to implement reinvokeBuilders(), but got marked dirty.'); + throw new WidgetError( + '$runtimeType failed to implement reinvokeBuilders(), but got marked dirty.\n' + 'If a RenderObjectElement subclass supports being marked dirty, then the ' + 'reinvokeBuilders() method must be implemented.\n' + 'If a RenderObjectElement uses a builder callback, it must support being ' + 'marked dirty, because builder callbacks can register the object as having ' + 'an Inherited dependency.' + ); }); } @@ -1812,7 +1838,11 @@ class MultiChildRenderObjectElement exte continue; // when these nodes are reordered, we just reassign the data if (!idSet.add(child.key)) { - throw new WidgetError('If multiple keyed nodes exist as children of another node, they must have unique keys. $widget has multiple children with key "${child.key}".'); + throw new WidgetError( + 'Duplicate keys found.\n' + 'If multiple keyed nodes exist as children of another node, they must have unique keys.\n' + '$widget has multiple children with key "${child.key}".' + ); } } return false; @@ -1841,16 +1871,10 @@ class MultiChildRenderObjectElement exte } } -class WidgetError extends Error { - WidgetError(String message, [ String rawDetails = '' ]) { - rawDetails = rawDetails.trimRight(); // remove trailing newlines - if (rawDetails != '') - _message = '$message\n$rawDetails'; - else - _message = message; - } - String _message; - String toString() => _message; +class WidgetError extends AssertionError { + WidgetError(this.message); + final String message; + String toString() => message; } typedef void WidgetsExceptionHandler(String context, dynamic exception, StackTrace stack); diff --git a/packages/flutter/lib/src/widgets/gesture_detector.dart b/packages/flutter/lib/src/widgets/gesture_detector.dart index 58a4567af4..c59575c99f 100644 --- a/packages/flutter/lib/src/widgets/gesture_detector.dart +++ b/packages/flutter/lib/src/widgets/gesture_detector.dart @@ -71,11 +71,20 @@ class GestureDetector extends StatelessComponent { bool havePan = onPanStart != null || onPanUpdate != null || onPanEnd != null; bool haveScale = onScaleStart != null || onScaleUpdate != null || onScaleEnd != null; if (havePan || haveScale) { - if (havePan && haveScale) - throw new WidgetError('Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan. Just use the scale gesture recognizer.'); + if (havePan && haveScale) { + throw new WidgetError( + 'Incorrect GestureDetector arguments.\n' + 'Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan. Just use the scale gesture recognizer.' + ); + } String recognizer = havePan ? 'pan' : 'scale'; - if (haveVerticalDrag && haveHorizontalDrag) - throw new WidgetError('Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.'); + if (haveVerticalDrag && haveHorizontalDrag) { + throw new WidgetError( + 'Incorrect GestureDetector arguments.\n' + 'Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer ' + 'will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.' + ); + } } return true; }); @@ -279,8 +288,15 @@ class RawGestureDetectorState extends State { /// the gesture detector should be enabled. void replaceGestureRecognizers(Map gestures) { assert(() { - if (!RenderObject.debugDoingLayout) - throw new WidgetError('replaceGestureRecognizers() can only be called during the layout phase.'); + if (!RenderObject.debugDoingLayout) { + throw new WidgetError( + 'Unexpected call to replaceGestureRecognizers() method of RawGestureDetectorState.\n' + 'The replaceGestureRecognizers() method can only be called during the layout phase. ' + 'To set the gesture recognisers at other times, trigger a new build using setState() ' + 'and provide the new gesture recognisers as constructor arguments to the corresponding ' + 'RawGestureDetector or GestureDetector object.' + ); + } return true; }); _syncAll(gestures); diff --git a/packages/flutter/lib/src/widgets/heroes.dart b/packages/flutter/lib/src/widgets/heroes.dart index 8f13cdc36e..69ffc8bdfb 100644 --- a/packages/flutter/lib/src/widgets/heroes.dart +++ b/packages/flutter/lib/src/widgets/heroes.dart @@ -113,8 +113,8 @@ class Hero extends StatefulComponent { if (tagHeroes.containsKey(key)) { new WidgetError( 'There are multiple heroes that share the same key within the same subtree.\n' - 'Within each subtree for which heroes are to be animated (typically a PageRoute subtree),\n' - 'either each Hero must have a unique tag, or, all the heroes with a particular tag must\n' + 'Within each subtree for which heroes are to be animated (typically a PageRoute subtree), ' + 'either each Hero must have a unique tag, or, all the heroes with a particular tag must ' 'have different keys.\n' 'In this case, the tag "$tag" had multiple heroes with the key "$key".' ); diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart index f293c960e2..a3941263ce 100644 --- a/packages/flutter/lib/src/widgets/navigator.dart +++ b/packages/flutter/lib/src/widgets/navigator.dart @@ -260,8 +260,12 @@ class Navigator extends StatefulComponent { static void openTransaction(BuildContext context, NavigatorTransactionCallback callback) { NavigatorState navigator = context.ancestorStateOfType(const TypeMatcher()); assert(() { - if (navigator == null) - throw new WidgetError('openTransaction called with a context that does not include a Navigator. The context passed to the Navigator.openTransaction() method must be that of a widget that is a descendant of a Navigator widget.'); + if (navigator == null) { + throw new WidgetError( + 'openTransaction called with a context that does not include a Navigator.\n' + 'The context passed to the Navigator.openTransaction() method must be that of a widget that is a descendant of a Navigator widget.' + ); + } return true; }); navigator.openTransaction(callback);