diff --git a/packages/flutter/lib/src/material/debug.dart b/packages/flutter/lib/src/material/debug.dart index 9c081c0364..a2ea646049 100644 --- a/packages/flutter/lib/src/material/debug.dart +++ b/packages/flutter/lib/src/material/debug.dart @@ -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)}' ); diff --git a/packages/flutter/lib/src/rendering/debug.dart b/packages/flutter/lib/src/rendering/debug.dart index 396b98bc83..a0950a68bc 100644 --- a/packages/flutter/lib/src/rendering/debug.dart +++ b/packages/flutter/lib/src/rendering/debug.dart @@ -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; diff --git a/packages/flutter/lib/src/rendering/error.dart b/packages/flutter/lib/src/rendering/error.dart index 4e4b8c2e94..e6c3d00258 100644 --- a/packages/flutter/lib/src/rendering/error.dart +++ b/packages/flutter/lib/src/rendering/error.dart @@ -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)); } - void paint(PaintingContext context, Offset offset) { - context.canvas.drawRect(offset & size, new Paint() .. color = debugErrorBoxColor); - } + /// 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) { + 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) { } + } } diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index d085a24b8a..35358bec45 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -1016,8 +1016,24 @@ abstract class Element 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 extends BuildableElement { }); } 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 extends BuildableElement { assert(_child != null); } catch (e, stack) { _debugReportException('building $_widget', e, stack); - built = new ErrorWidget(); + built = new ErrorWidget(e); _child = updateChild(null, built, slot); } } diff --git a/packages/flutter_test/lib/src/widget_tester.dart b/packages/flutter_test/lib/src/widget_tester.dart index a47842e71f..50e8e30d43 100644 --- a/packages/flutter_test/lib/src/widget_tester.dart +++ b/packages/flutter_test/lib/src/widget_tester.dart @@ -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;