Commit f04fd43f authored by Ian Hickson's avatar Ian Hickson

Merge pull request #2461 from Hixie/ErrorWidget

Include more details in the Red Box of Doom
parents a1535d05 826b13bd
......@@ -13,12 +13,14 @@ bool debugCheckHasMaterial(BuildContext context) {
Element element = context;
throw new WidgetError(
'No Material widget found.\n'
'${context.widget} widgets require a Material widget ancestor.\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, '
'that material is represented by the Material widget. It is the Material widget that renders ink splashes, for instance. '
'Because of this, many material library widgets require that there be a Material widget in the tree above them.\n'
'To introduce a Material widget, you can either directly include one, or use a widget that contains Material itself, '
'such as a Card, Dialog, Drawer, or Scaffold.\n'
'The specific widget that could not find a Material ancestor was:\n'
' ${context.widget}'
'The ownership chain for the affected widget is:\n'
' ${element.debugGetOwnershipChain(10)}'
);
......@@ -35,7 +37,9 @@ bool debugCheckHasScaffold(BuildContext context) {
Element element = context;
throw new WidgetError(
'No Scaffold widget found.\n'
'${context.widget} widgets require a Scaffold widget ancestor.\n'
'${context.widget.runtimeType} widgets require a Scaffold widget ancestor.\n'
'The specific widget that could not find a Scaffold ancestor was:\n'
' ${context.widget}'
'The ownership chain for the affected widget is:\n'
' ${element.debugGetOwnershipChain(10)}'
);
......
......@@ -53,9 +53,6 @@ bool debugPaintPointersEnabled = false;
/// The color to use when reporting pointers.
int debugPaintPointersColorValue = 0x00BBBB;
/// The color to use when painting [RenderErrorBox] objects in checked mode.
Color debugErrorBoxColor = const Color(0xFFFF0000);
/// Overlay a rotating set of colors when repainting layers in checked mode.
bool debugRepaintRainbowEnabled = false;
......
......@@ -2,15 +2,56 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' as ui show Paragraph, ParagraphBuilder, ParagraphStyle, TextStyle;
import 'box.dart';
import 'debug.dart';
import 'object.dart';
const double _kMaxWidth = 100000.0;
const double _kMaxHeight = 100000.0;
/// A render object used as a placeholder when an error occurs
/// A render object used as a placeholder when an error occurs.
///
/// The box will be painted in the color given by the
/// [RenderErrorBox.backgroundColor] static property.
///
/// A message can be provided. To simplify the class and thus help reduce the
/// likelihood of this class itself being the source of errors, the message
/// cannot be changed once the object has been created. If provided, the text
/// will be painted on top of the background, using the styles given by the
/// [RenderErrorBox.textStyle] and [RenderErrorBox.paragraphStyle] static
/// properties.
///
/// Again to help simplify the class, this box tries to be 100000.0 pixels wide
/// and high, to approximate being infinitely high but without using infinities.
class RenderErrorBox extends RenderBox {
/// Constructs a RenderErrorBox render object.
///
/// A message can optionally be provided. If a message is provided, an attempt
/// will be made to render the message when the box paints.
RenderErrorBox([ this.message = '' ]) {
try {
if (message != '') {
// This class is intentionally doing things using the low-level
// primitives to avoid depending on any subsystems that may have ended
// up in an unstable state -- after all, this class is mainly used when
// things have gone wrong.
//
// Generally, the much better way to draw text in a RenderObject is to
// use the TextPainter class. If you're looking for code to crib from,
// see the paragraph.dart file and the RenderParagraph class.
ui.ParagraphBuilder builder = new ui.ParagraphBuilder();
builder.pushStyle(textStyle);
builder.addText(message);
_paragraph = builder.build(paragraphStyle);
}
} catch (e) { }
}
/// The message to attempt to display at paint time.
final String message;
ui.Paragraph _paragraph;
double getMinIntrinsicWidth(BoxConstraints constraints) {
return constraints.constrainWidth(0.0);
......@@ -36,8 +77,36 @@ class RenderErrorBox extends RenderBox {
size = constraints.constrain(const Size(_kMaxWidth, _kMaxHeight));
}
/// The color to use when painting the background of [RenderErrorBox] objects.
static Color backgroundColor = const Color(0xF0900000);
/// The text style to use when painting [RenderErrorBox] objects.
static ui.TextStyle textStyle = new ui.TextStyle(
color: const Color(0xFFFFFF00),
fontFamily: 'monospace',
fontSize: 7.0
);
/// The paragraph style to use when painting [RenderErrorBox] objects.
static ui.ParagraphStyle paragraphStyle = new ui.ParagraphStyle(
lineHeight: 0.25 // TODO(ianh): https://github.com/flutter/flutter/issues/2460 will affect this
);
void paint(PaintingContext context, Offset offset) {
context.canvas.drawRect(offset & size, new Paint() .. color = debugErrorBoxColor);
try {
context.canvas.drawRect(offset & size, new Paint() .. color = backgroundColor);
if (_paragraph != null) {
// See the comment in the RenderErrorBox constructor. This is not the
// code you want to be copying and pasting. :-)
if (parent is RenderBox) {
RenderBox parentBox = parent;
_paragraph.maxWidth = parentBox.size.width;
} else {
_paragraph.maxWidth = size.width;
}
_paragraph.layout();
_paragraph.paint(context.canvas, offset);
}
} catch (e) { }
}
}
......@@ -1016,8 +1016,24 @@ abstract class Element<T extends Widget> implements BuildContext {
}
}
/// A widget that renders an exception's message. This widget is used when a
/// build function fails, to help with determining where the problem lies.
/// Exceptions are also logged to the console, which you can read using `flutter
/// logs`. The console will also include additional information such as the
/// stack trace for the exception.
class ErrorWidget extends LeafRenderObjectWidget {
RenderBox createRenderObject() => new RenderErrorBox();
ErrorWidget(
Object exception
) : message = _stringify(exception),
super(key: new UniqueKey());
final String message;
static String _stringify(Object exception) {
try {
return exception.toString();
} catch (e) { }
return 'Error';
}
RenderBox createRenderObject() => new RenderErrorBox(message);
}
typedef void BuildScheduler(BuildableElement element);
......@@ -1224,7 +1240,7 @@ abstract class ComponentElement<T extends Widget> extends BuildableElement<T> {
});
} catch (e, stack) {
_debugReportException('building $_widget', e, stack);
built = new ErrorWidget();
built = new ErrorWidget(e);
} finally {
// We delay marking the element as clean until after calling _builder so
// that attempts to markNeedsBuild() during build() will be ignored.
......@@ -1236,7 +1252,7 @@ abstract class ComponentElement<T extends Widget> extends BuildableElement<T> {
assert(_child != null);
} catch (e, stack) {
_debugReportException('building $_widget', e, stack);
built = new ErrorWidget();
built = new ErrorWidget(e);
_child = updateChild(null, built, slot);
}
}
......
......@@ -78,7 +78,7 @@ class WidgetTester extends Instrumentation {
super(binding: _SteppedWidgetFlutterBinding.ensureInitialized()) {
timeDilation = 1.0;
ui.window.onBeginFrame = null;
runApp(new ErrorWidget()); // flush out the last build entirely
runApp(new Container(key: new UniqueKey())); // flush out the last build entirely
}
final FakeAsync async;
......
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