Commit 7e3bef11 authored by Hixie's avatar Hixie

Replace WidgetError and RenderingError with FlutterError

parent 5a141172
......@@ -11,7 +11,7 @@ bool debugCheckHasMaterial(BuildContext context) {
assert(() {
if (context.widget is! Material && context.ancestorWidgetOfExactType(Material) == null) {
Element element = context;
throw new WidgetError(
throw new FlutterError(
'No Material widget found.\n'
'${context.widget.runtimeType} widgets require a Material widget ancestor.\n'
'In material design, most widgets are conceptually "printed" on a sheet of material. In Flutter\'s material library, '
......@@ -35,7 +35,7 @@ bool debugCheckHasScaffold(BuildContext context) {
assert(() {
if (Scaffold.of(context) == null) {
Element element = context;
throw new WidgetError(
throw new FlutterError(
'No Scaffold widget found.\n'
'${context.widget.runtimeType} widgets require a Scaffold widget ancestor.\n'
'The specific widget that could not find a Scaffold ancestor was:\n'
......
......@@ -306,17 +306,17 @@ class BoxConstraints extends Constraints {
bool get debugAssertIsNormalized {
assert(() {
if (minWidth < 0.0 && minHeight < 0.0)
throw new RenderingError('BoxConstraints has both a negative minimum width and a negative minimum height.\n$this');
throw new FlutterError('BoxConstraints has both a negative minimum width and a negative minimum height.\n$this');
if (minWidth < 0.0)
throw new RenderingError('BoxConstraints has a negative minimum width.\n$this');
throw new FlutterError('BoxConstraints has a negative minimum width.\n$this');
if (minHeight < 0.0)
throw new RenderingError('BoxConstraints has a negative minimum height.\n$this');
throw new FlutterError('BoxConstraints has a negative minimum height.\n$this');
if (maxWidth < minWidth && maxHeight < minHeight)
throw new RenderingError('BoxConstraints has both width and height constraints non-normalized.\n$this');
throw new FlutterError('BoxConstraints has both width and height constraints non-normalized.\n$this');
if (maxWidth < minWidth)
throw new RenderingError('BoxConstraints has non-normalized width constraints.\n$this');
throw new FlutterError('BoxConstraints has non-normalized width constraints.\n$this');
if (maxHeight < minHeight)
throw new RenderingError('BoxConstraints has non-normalized height constraints.\n$this');
throw new FlutterError('BoxConstraints has non-normalized height constraints.\n$this');
return isNormalized;
});
return isNormalized;
......@@ -636,7 +636,7 @@ abstract class RenderBox extends RenderObject {
for (String line in description)
information.writeln(' $line');
}
throw new RenderingError(
throw new FlutterError(
'$runtimeType object was given an infinite size during layout.\n'
'This probably means that it is a render object that tries to be\n'
'as big as possible, but it was put inside another render object\n'
......@@ -647,7 +647,7 @@ abstract class RenderBox extends RenderObject {
}
// verify that the size is within the constraints
if (!constraints.isSatisfiedBy(_size)) {
throw new RenderingError(
throw new FlutterError(
'$runtimeType does not meet its constraints.\n'
'Constraints: $constraints\n'
'Size: $_size\n'
......@@ -684,7 +684,7 @@ abstract class RenderBox extends RenderObject {
RenderObject.debugCheckingIntrinsics = false;
if (failures.isNotEmpty) {
assert(failureCount > 0);
throw new RenderingError(
throw new FlutterError(
'The intrinsic dimension methods of the $runtimeType class returned values that violate the given constraints.\n'
'The constraints were: $constraints\n'
'The following method${failureCount > 1 ? "s" : ""} returned values outside of those constraints:\n'
......
......@@ -45,13 +45,13 @@ abstract class MultiChildLayoutDelegate {
final RenderBox child = _idToChild[childId];
assert(() {
if (child == null) {
throw new RenderingError(
throw new FlutterError(
'The $this custom multichild layout delegate tried to lay out a non-existent child:\n'
'There is no child with the id "$childId".'
);
}
if (!_debugChildrenNeedingLayout.remove(child)) {
throw new RenderingError(
throw new FlutterError(
'The $this custom multichild layout delegate tried to lay out the child with id "$childId" more than once.\n'
'Each child must be laid out exactly once.'
);
......@@ -59,7 +59,7 @@ abstract class MultiChildLayoutDelegate {
try {
assert(constraints.debugAssertIsNormalized);
} on AssertionError catch (exception) {
throw new RenderingError(
throw new FlutterError(
'The $this custom multichild layout delegate provided invalid box constraints for the child with id "$childId":\n'
'$exception\n'
'The minimum width and height must be greater than or equal to zero.\n'
......@@ -83,13 +83,13 @@ abstract class MultiChildLayoutDelegate {
final RenderBox child = _idToChild[childId];
assert(() {
if (child == null) {
throw new RenderingError(
throw new FlutterError(
'The $this custom multichild layout delegate tried to position out a non-existent child:\n'
'There is no child with the id "$childId".'
);
}
if (offset == null) {
throw new RenderingError(
throw new FlutterError(
'The $this custom multichild layout delegate provided a null position for the child with id "$childId".'
);
}
......@@ -124,7 +124,7 @@ abstract class MultiChildLayoutDelegate {
final MultiChildLayoutParentData childParentData = child.parentData;
assert(() {
if (childParentData.id == null) {
throw new RenderingError(
throw new FlutterError(
'The following child has no ID:\n'
' $child\n'
'Every child of a RenderCustomMultiChildLayoutBox must have an ID in its parent data.'
......@@ -143,13 +143,13 @@ abstract class MultiChildLayoutDelegate {
assert(() {
if (_debugChildrenNeedingLayout.isNotEmpty) {
if (_debugChildrenNeedingLayout.length > 1) {
throw new RenderingError(
throw new FlutterError(
'The $this custom multichild layout delegate forgot to lay out the following children:\n'
' ${_debugChildrenNeedingLayout.map(_debugDescribeChild).join("\n ")}\n'
'Each child must be laid out exactly once.'
);
} else {
throw new RenderingError(
throw new FlutterError(
'The $this custom multichild layout delegate forgot to lay out the following child:\n'
' ${_debugDescribeChild(_debugChildrenNeedingLayout.single)}\n'
'Each child must be laid out exactly once.'
......
......@@ -399,7 +399,7 @@ class RenderGrid extends RenderVirtualViewport<GridParentData> {
void set mainAxis(Axis value) {
assert(() {
if (value != Axis.vertical)
throw new RenderingError('RenderGrid doesn\'t yet support horizontal scrolling.');
throw new FlutterError('RenderGrid doesn\'t yet support horizontal scrolling.');
return true;
});
super.mainAxis = value;
......
......@@ -20,6 +20,7 @@ import 'binding.dart';
export 'package:flutter/gestures.dart' show HitTestEntry, HitTestResult;
export 'package:flutter/painting.dart';
export 'package:flutter/services.dart' show FlutterError;
/// Base class for data associated with a [RenderObject] by its parent.
///
......@@ -2089,13 +2090,3 @@ abstract class ContainerRenderObjectMixin<ChildType extends RenderObject, Parent
return result;
}
}
/// Error thrown when the rendering library encounters a contract violation.
class RenderingError extends AssertionError {
RenderingError(this.message);
final String message;
@override
String toString() => message;
}
......@@ -441,7 +441,7 @@ class RenderAspectRatio extends RenderProxyBox {
assert(constraints.debugAssertIsNormalized);
assert(() {
if (!constraints.hasBoundedWidth && !constraints.hasBoundedHeight) {
throw new RenderingError(
throw new FlutterError(
'$runtimeType has unbounded constraints.\n'
'This $runtimeType was given an aspect ratio of $aspectRatio but was given '
'both unbounded width and unbounded height constraints. Because both '
......@@ -1386,7 +1386,7 @@ class RenderCustomPaint extends RenderProxyBox {
// below that number.
int debugNewCanvasSaveCount = canvas.getSaveCount();
if (debugNewCanvasSaveCount > debugPreviousCanvasSaveCount) {
throw new RenderingError(
throw new FlutterError(
'The $painter custom painter called canvas.save() or canvas.saveLayer() at least '
'${debugNewCanvasSaveCount - debugPreviousCanvasSaveCount} more '
'time${debugNewCanvasSaveCount - debugPreviousCanvasSaveCount == 1 ? '' : 's' } '
......@@ -1396,7 +1396,7 @@ class RenderCustomPaint extends RenderProxyBox {
);
}
if (debugNewCanvasSaveCount < debugPreviousCanvasSaveCount) {
throw new RenderingError(
throw new FlutterError(
'The $painter custom painter called canvas.restore() '
'${debugPreviousCanvasSaveCount - debugNewCanvasSaveCount} more '
'time${debugPreviousCanvasSaveCount - debugNewCanvasSaveCount == 1 ? '' : 's' } '
......
......@@ -2,9 +2,32 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// Error class used to report Flutter-specific assertion failures and
/// contract violations.
class FlutterError extends AssertionError {
/// Creates a [FlutterError].
///
/// See [message] for details on the format that the message should
/// take.
///
/// Include as much detail as possible in the full error message,
/// including specifics about the state of the app that might be
/// relevant to debugging the error.
FlutterError(this.message);
/// The message associated with this error.
///
/// The message may have newlines in it. The first line should be a
/// terse description of the error, e.g. "Incorrect GlobalKey usage"
/// or "setState() or markNeedsBuild() called during build".
/// Subsequent lines can then contain more information. In some
/// cases, when a FlutterError is reported to the user, only the
/// first line is included. For example, Flutter will typically only
/// fully report the first exception at runtime, displaying only the
/// first line of subsequent errors.
///
/// All sentences in the error should be correctly punctuated (i.e.,
/// do end the error message with a period).
final String message;
@override
......
......@@ -291,7 +291,7 @@ class _DismissableState extends State<Dismissable> {
assert(() {
if (_resizeAnimation.status != AnimationStatus.forward) {
assert(_resizeAnimation.status == AnimationStatus.completed);
throw new WidgetError(
throw new FlutterError(
'A dismissed Dismissable widget is still part of the tree.\n' +
'Make sure to implement the onDismissed handler and to immediately remove the Dismissable\n' +
'widget from the application once that handler has fired.'
......
......@@ -9,6 +9,7 @@ import 'package:flutter/rendering.dart';
export 'dart:ui' show hashValues, hashList;
export 'package:flutter/rendering.dart' show RenderObject, RenderBox, debugPrint;
export 'package:flutter/services.dart' show FlutterError;
// KEYS
......@@ -178,7 +179,7 @@ abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
message += 'The most recently registered instance is: ${_registry[key]}\n';
}
if (_debugDuplicates.isNotEmpty) {
throw new WidgetError(
throw new FlutterError(
'Incorrect GlobalKey usage.\n'
'$message'
);
......@@ -380,7 +381,7 @@ abstract class State<T extends StatefulWidget> {
void setState(VoidCallback fn) {
assert(() {
if (_debugLifecycleState == _StateLifecycle.defunct) {
throw new WidgetError(
throw new FlutterError(
'setState() called after dipose(): $this\n'
'This error happens if you call setState() on State object for a widget that\n'
'no longer appears in the widget tree (e.g., whose parent widget no longer\n'
......@@ -1197,7 +1198,7 @@ abstract class BuildableElement extends Element {
return true;
}
if (_debugStateLocked && (!_debugAllowIgnoredCallsToMarkNeedsBuild || !dirty)) {
throw new WidgetError(
throw new FlutterError(
'setState() or markNeedsBuild() called during build.\n'
'This widget cannot be marked as needing to build because the framework '
'is already in the process of building widgets. A widget can be marked as '
......@@ -1296,7 +1297,7 @@ abstract class ComponentElement extends BuildableElement {
built = _builder(this);
assert(() {
if (built == null) {
throw new WidgetError(
throw new FlutterError(
'A build function returned null.\n'
'The offending widget is: $widget\n'
'Build functions must never return null. '
......@@ -1388,7 +1389,7 @@ class StatefulElement extends ComponentElement {
assert(() {
if (_state._debugLifecycleState == _StateLifecycle.initialized)
return true;
throw new WidgetError(
throw new FlutterError(
'${_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.'
......@@ -1430,7 +1431,7 @@ class StatefulElement extends ComponentElement {
assert(() {
if (_state._debugLifecycleState == _StateLifecycle.defunct)
return true;
throw new WidgetError(
throw new FlutterError(
'${_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.'
......@@ -1503,7 +1504,7 @@ class ParentDataElement<T extends RenderObjectWidget> extends _ProxyElement {
}
if (ancestor != null && badAncestors.isEmpty)
return true;
throw new WidgetError(
throw new FlutterError(
'Incorrect use of ParentDataWidget.\n' +
widget.debugDescribeInvalidAncestorChain(
description: "$this",
......@@ -1975,7 +1976,7 @@ class MultiChildRenderObjectElement extends RenderObjectElement {
continue; // when these nodes are reordered, we just reassign the data
if (!idSet.add(child.key)) {
throw new WidgetError(
throw new FlutterError(
'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}".'
......@@ -2021,14 +2022,6 @@ class MultiChildRenderObjectElement extends RenderObjectElement {
}
}
class WidgetError extends AssertionError {
WidgetError(this.message);
final String message;
@override
String toString() => message;
}
typedef void WidgetsExceptionHandler(String context, dynamic exception, StackTrace stack);
/// This callback is invoked whenever an exception is caught by the widget
/// system. The 'context' argument is a description of what was happening when
......
......@@ -79,14 +79,14 @@ class GestureDetector extends StatelessWidget {
bool haveScale = onScaleStart != null || onScaleUpdate != null || onScaleEnd != null;
if (havePan || haveScale) {
if (havePan && haveScale) {
throw new WidgetError(
throw new FlutterError(
'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(
throw new FlutterError(
'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.'
......@@ -336,7 +336,7 @@ class RawGestureDetectorState extends State<RawGestureDetector> {
void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) {
assert(() {
if (!RenderObject.debugDoingLayout) {
throw new WidgetError(
throw new FlutterError(
'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() '
......
......@@ -115,7 +115,7 @@ class Hero extends StatefulWidget {
final Map<Key, HeroState> tagHeroes = heroes.putIfAbsent(tag, () => <Key, HeroState>{});
assert(() {
if (tagHeroes.containsKey(key)) {
new WidgetError(
new FlutterError(
'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), '
'either each Hero must have a unique tag, or, all the heroes with a particular tag must '
......
......@@ -181,7 +181,7 @@ class MimicableState extends State<Mimicable> {
MimicableHandle startMimic() {
assert(() {
if (_placeholderSize != null) {
throw new WidgetError(
throw new FlutterError(
'Mimicable started while already active.\n'
'When startMimic() or liftToOverlay() is called on a MimicableState, the mimic becomes active. '
'While active, it cannot be reactivated until it is stopped. '
......
......@@ -262,7 +262,7 @@ class Navigator extends StatefulWidget {
NavigatorState navigator = context.ancestorStateOfType(const TypeMatcher<NavigatorState>());
assert(() {
if (navigator == null) {
throw new WidgetError(
throw new FlutterError(
'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.'
);
......
......@@ -90,7 +90,7 @@ class Overlay extends StatefulWidget {
String additional = context.widget != debugRequiredFor
? '\nThe context from which that widget was searching for an overlay was:\n $context'
: '';
throw new WidgetError(
throw new FlutterError(
'No Overlay widget found.\n'
'${debugRequiredFor.runtimeType} widgets require an Overlay widget ancestor for correct operation.\n'
'The most common way to add an Overlay to an application is to include a MaterialApp or Navigator widget in the runApp() call.\n'
......
......@@ -113,7 +113,7 @@ void main() {
);
tester.pumpWidget(widget);
tester.tap(tester.findElementByKey(targetKey));
expect(exception, new isInstanceOf<WidgetError>());
expect(exception, new isInstanceOf<FlutterError>());
expect('$exception', startsWith('openTransaction called with a context'));
});
});
......
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