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> {
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.'
);
}
......
......@@ -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 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<T extends Widget> extends Element<T> {
}
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<T extends Widget> extends BuildableElement<T> {
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<T extends StatefulComponent, U extends State<T>>
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<T extends StatefulComponent, U extends State<T>>
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<ParentDataWidget> {
}
if (ancestor != null && badAncestors.isEmpty)
return true;
throw new WidgetError('Incorrect use of ParentDataWidget.', widget.debugDescribeInvalidAncestorChain(
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<T extends RenderObjectWidget> 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<T extends MultiChildRenderObjectWidget> 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<T extends MultiChildRenderObjectWidget> 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);
......
......@@ -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<RawGestureDetector> {
/// the gesture detector should be enabled.
void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> 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);
......
......@@ -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".'
);
......
......@@ -260,8 +260,12 @@ class Navigator extends StatefulComponent {
static void openTransaction(BuildContext context, NavigatorTransactionCallback callback) {
NavigatorState navigator = context.ancestorStateOfType(const TypeMatcher<NavigatorState>());
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);
......
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