Commit 31bdad08 authored by Ian Hickson's avatar Ian Hickson

Merge pull request #2454 from Hixie/widgeterror

Remove the second argument to WidgetError.
parents 60ca6f1c ef9132a0
...@@ -256,7 +256,7 @@ class _DismissableState extends State<Dismissable> { ...@@ -256,7 +256,7 @@ class _DismissableState extends State<Dismissable> {
assert(_resizeAnimation.status == AnimationStatus.completed); assert(_resizeAnimation.status == AnimationStatus.completed);
throw new WidgetError( throw new WidgetError(
'Dismissable widget completed its resize animation without being removed from the tree.\n' '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.' 'widget from the application once that handler has fired.'
); );
} }
......
...@@ -149,8 +149,12 @@ abstract class GlobalKey<T extends State<StatefulComponent>> extends Key { ...@@ -149,8 +149,12 @@ abstract class GlobalKey<T extends State<StatefulComponent>> extends Key {
message += 'The following GlobalKey was found multiple times among mounted elements: $key (${_debugDuplicates[key]} instances)\n'; 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'; message += 'The most recently registered instance is: ${_registry[key]}\n';
} }
if (!_debugDuplicates.isEmpty) if (!_debugDuplicates.isEmpty) {
throw new WidgetError('Incorrect GlobalKey usage.', message); throw new WidgetError(
'Incorrect GlobalKey usage.\n'
'$message'
);
}
return true; return true;
} }
...@@ -1109,9 +1113,10 @@ abstract class BuildableElement<T extends Widget> extends Element<T> { ...@@ -1109,9 +1113,10 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
} }
if (_debugStateLocked && (!_debugAllowIgnoredCallsToMarkNeedsBuild || !dirty)) { if (_debugStateLocked && (!_debugAllowIgnoredCallsToMarkNeedsBuild || !dirty)) {
throw new WidgetError( throw new WidgetError(
'Cannot mark this component as needing to build because the framework is ' 'setState() or markNeedsBuild() called during build.\n'
'already in the process of building widgets. A widget can be marked as ' 'This component cannot be marked as needing to build because the framework '
'needing to be built during the build phase only if one if its ancestor ' '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 ' 'is currently building. This exception is allowed because the framework '
'builds parent widgets before children, which means a dirty descendant ' 'builds parent widgets before children, which means a dirty descendant '
'will always be built. Otherwise, the framework might not visit this ' 'will always be built. Otherwise, the framework might not visit this '
...@@ -1208,8 +1213,11 @@ abstract class ComponentElement<T extends Widget> extends BuildableElement<T> { ...@@ -1208,8 +1213,11 @@ abstract class ComponentElement<T extends Widget> extends BuildableElement<T> {
assert(() { assert(() {
if (built == null) { if (built == null) {
throw new WidgetError( throw new WidgetError(
'A build function returned null. Build functions must never return null.', 'A build function returned null.\n'
'The offending widget is: $widget' '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; return true;
...@@ -1289,7 +1297,11 @@ class StatefulComponentElement<T extends StatefulComponent, U extends State<T>> ...@@ -1289,7 +1297,11 @@ class StatefulComponentElement<T extends StatefulComponent, U extends State<T>>
assert(() { assert(() {
if (_state._debugLifecycleState == _StateLifecycle.initialized) if (_state._debugLifecycleState == _StateLifecycle.initialized)
return true; 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; }); assert(() { _state._debugLifecycleState = _StateLifecycle.ready; return true; });
super._firstBuild(); super._firstBuild();
...@@ -1324,7 +1336,11 @@ class StatefulComponentElement<T extends StatefulComponent, U extends State<T>> ...@@ -1324,7 +1336,11 @@ class StatefulComponentElement<T extends StatefulComponent, U extends State<T>>
assert(() { assert(() {
if (_state._debugLifecycleState == _StateLifecycle.defunct) if (_state._debugLifecycleState == _StateLifecycle.defunct)
return true; 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. assert(!dirty); // See BuildableElement.unmount for why this is important.
_state._element = null; _state._element = null;
...@@ -1381,12 +1397,15 @@ class ParentDataElement extends _ProxyElement<ParentDataWidget> { ...@@ -1381,12 +1397,15 @@ class ParentDataElement extends _ProxyElement<ParentDataWidget> {
} }
if (ancestor != null && badAncestors.isEmpty) if (ancestor != null && badAncestors.isEmpty)
return true; return true;
throw new WidgetError('Incorrect use of ParentDataWidget.', widget.debugDescribeInvalidAncestorChain( throw new WidgetError(
description: "$this", 'Incorrect use of ParentDataWidget.\n' +
ownershipChain: parent.debugGetOwnershipChain(10), widget.debugDescribeInvalidAncestorChain(
foundValidAncestor: ancestor != null, description: "$this",
badAncestors: badAncestors ownershipChain: parent.debugGetOwnershipChain(10),
)); foundValidAncestor: ancestor != null,
badAncestors: badAncestors
)
);
}); });
super.mount(parent, slot); super.mount(parent, slot);
} }
...@@ -1507,7 +1526,14 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab ...@@ -1507,7 +1526,14 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab
// 'BuildContext' argument which you can pass to Theme.of() and other // 'BuildContext' argument which you can pass to Theme.of() and other
// InheritedWidget APIs which eventually trigger a rebuild.) // InheritedWidget APIs which eventually trigger a rebuild.)
assert(() { 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<T extends MultiChildRenderObjectWidget> exte ...@@ -1812,7 +1838,11 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte
continue; // when these nodes are reordered, we just reassign the data continue; // when these nodes are reordered, we just reassign the data
if (!idSet.add(child.key)) { 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; return false;
...@@ -1841,16 +1871,10 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte ...@@ -1841,16 +1871,10 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte
} }
} }
class WidgetError extends Error { class WidgetError extends AssertionError {
WidgetError(String message, [ String rawDetails = '' ]) { WidgetError(this.message);
rawDetails = rawDetails.trimRight(); // remove trailing newlines final String message;
if (rawDetails != '') String toString() => message;
_message = '$message\n$rawDetails';
else
_message = message;
}
String _message;
String toString() => _message;
} }
typedef void WidgetsExceptionHandler(String context, dynamic exception, StackTrace stack); typedef void WidgetsExceptionHandler(String context, dynamic exception, StackTrace stack);
......
...@@ -71,11 +71,20 @@ class GestureDetector extends StatelessComponent { ...@@ -71,11 +71,20 @@ class GestureDetector extends StatelessComponent {
bool havePan = onPanStart != null || onPanUpdate != null || onPanEnd != null; bool havePan = onPanStart != null || onPanUpdate != null || onPanEnd != null;
bool haveScale = onScaleStart != null || onScaleUpdate != null || onScaleEnd != null; bool haveScale = onScaleStart != null || onScaleUpdate != null || onScaleEnd != null;
if (havePan || haveScale) { if (havePan || haveScale) {
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.'); 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'; String recognizer = havePan ? 'pan' : 'scale';
if (haveVerticalDrag && haveHorizontalDrag) 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.'); 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; return true;
}); });
...@@ -279,8 +288,15 @@ class RawGestureDetectorState extends State<RawGestureDetector> { ...@@ -279,8 +288,15 @@ class RawGestureDetectorState extends State<RawGestureDetector> {
/// the gesture detector should be enabled. /// the gesture detector should be enabled.
void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) { void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) {
assert(() { assert(() {
if (!RenderObject.debugDoingLayout) if (!RenderObject.debugDoingLayout) {
throw new WidgetError('replaceGestureRecognizers() can only be called during the layout phase.'); 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; return true;
}); });
_syncAll(gestures); _syncAll(gestures);
......
...@@ -113,8 +113,8 @@ class Hero extends StatefulComponent { ...@@ -113,8 +113,8 @@ class Hero extends StatefulComponent {
if (tagHeroes.containsKey(key)) { if (tagHeroes.containsKey(key)) {
new WidgetError( new WidgetError(
'There are multiple heroes that share the same key within the same subtree.\n' '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' '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\n' 'either each Hero must have a unique tag, or, all the heroes with a particular tag must '
'have different keys.\n' 'have different keys.\n'
'In this case, the tag "$tag" had multiple heroes with the key "$key".' 'In this case, the tag "$tag" had multiple heroes with the key "$key".'
); );
......
...@@ -260,8 +260,12 @@ class Navigator extends StatefulComponent { ...@@ -260,8 +260,12 @@ class Navigator extends StatefulComponent {
static void openTransaction(BuildContext context, NavigatorTransactionCallback callback) { static void openTransaction(BuildContext context, NavigatorTransactionCallback callback) {
NavigatorState navigator = context.ancestorStateOfType(const TypeMatcher<NavigatorState>()); NavigatorState navigator = context.ancestorStateOfType(const TypeMatcher<NavigatorState>());
assert(() { assert(() {
if (navigator == null) 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.'); 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; return true;
}); });
navigator.openTransaction(callback); navigator.openTransaction(callback);
......
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