Merge pull request #2461 from Hixie/ErrorWidget
Include more details in the Red Box of Doom
This commit is contained in:
commit
f04fd43fd9
@ -13,12 +13,14 @@ bool debugCheckHasMaterial(BuildContext context) {
|
|||||||
Element element = context;
|
Element element = context;
|
||||||
throw new WidgetError(
|
throw new WidgetError(
|
||||||
'No Material widget found.\n'
|
'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, '
|
'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. '
|
'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'
|
'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, '
|
'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'
|
'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'
|
'The ownership chain for the affected widget is:\n'
|
||||||
' ${element.debugGetOwnershipChain(10)}'
|
' ${element.debugGetOwnershipChain(10)}'
|
||||||
);
|
);
|
||||||
@ -35,7 +37,9 @@ bool debugCheckHasScaffold(BuildContext context) {
|
|||||||
Element element = context;
|
Element element = context;
|
||||||
throw new WidgetError(
|
throw new WidgetError(
|
||||||
'No Scaffold widget found.\n'
|
'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'
|
'The ownership chain for the affected widget is:\n'
|
||||||
' ${element.debugGetOwnershipChain(10)}'
|
' ${element.debugGetOwnershipChain(10)}'
|
||||||
);
|
);
|
||||||
|
@ -53,9 +53,6 @@ bool debugPaintPointersEnabled = false;
|
|||||||
/// The color to use when reporting pointers.
|
/// The color to use when reporting pointers.
|
||||||
int debugPaintPointersColorValue = 0x00BBBB;
|
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.
|
/// Overlay a rotating set of colors when repainting layers in checked mode.
|
||||||
bool debugRepaintRainbowEnabled = false;
|
bool debugRepaintRainbowEnabled = false;
|
||||||
|
|
||||||
|
@ -2,15 +2,56 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:ui' as ui show Paragraph, ParagraphBuilder, ParagraphStyle, TextStyle;
|
||||||
|
|
||||||
import 'box.dart';
|
import 'box.dart';
|
||||||
import 'debug.dart';
|
|
||||||
import 'object.dart';
|
import 'object.dart';
|
||||||
|
|
||||||
const double _kMaxWidth = 100000.0;
|
const double _kMaxWidth = 100000.0;
|
||||||
const double _kMaxHeight = 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 {
|
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) {
|
double getMinIntrinsicWidth(BoxConstraints constraints) {
|
||||||
return constraints.constrainWidth(0.0);
|
return constraints.constrainWidth(0.0);
|
||||||
@ -36,8 +77,36 @@ class RenderErrorBox extends RenderBox {
|
|||||||
size = constraints.constrain(const Size(_kMaxWidth, _kMaxHeight));
|
size = constraints.constrain(const Size(_kMaxWidth, _kMaxHeight));
|
||||||
}
|
}
|
||||||
|
|
||||||
void paint(PaintingContext context, Offset offset) {
|
/// The color to use when painting the background of [RenderErrorBox] objects.
|
||||||
context.canvas.drawRect(offset & size, new Paint() .. color = debugErrorBoxColor);
|
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) {
|
||||||
|
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 {
|
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);
|
typedef void BuildScheduler(BuildableElement element);
|
||||||
@ -1224,7 +1240,7 @@ abstract class ComponentElement<T extends Widget> extends BuildableElement<T> {
|
|||||||
});
|
});
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
_debugReportException('building $_widget', e, stack);
|
_debugReportException('building $_widget', e, stack);
|
||||||
built = new ErrorWidget();
|
built = new ErrorWidget(e);
|
||||||
} finally {
|
} finally {
|
||||||
// We delay marking the element as clean until after calling _builder so
|
// We delay marking the element as clean until after calling _builder so
|
||||||
// that attempts to markNeedsBuild() during build() will be ignored.
|
// 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);
|
assert(_child != null);
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
_debugReportException('building $_widget', e, stack);
|
_debugReportException('building $_widget', e, stack);
|
||||||
built = new ErrorWidget();
|
built = new ErrorWidget(e);
|
||||||
_child = updateChild(null, built, slot);
|
_child = updateChild(null, built, slot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ class WidgetTester extends Instrumentation {
|
|||||||
super(binding: _SteppedWidgetFlutterBinding.ensureInitialized()) {
|
super(binding: _SteppedWidgetFlutterBinding.ensureInitialized()) {
|
||||||
timeDilation = 1.0;
|
timeDilation = 1.0;
|
||||||
ui.window.onBeginFrame = null;
|
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;
|
final FakeAsync async;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user