Unverified Commit c28121ee authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Allow the ErrorWidget to be overridden. (#13578)

Fixes https://github.com/flutter/flutter/issues/10695
parent f9cf5a1f
......@@ -140,6 +140,38 @@ class FlutterErrorDetails {
longMessage = ' <no message available>';
return longMessage;
}
@override
String toString() {
final StringBuffer buffer = new StringBuffer();
if ((library != null && library != '') || (context != null && context != '')) {
if (library != null && library != '') {
buffer.write('Error caught by $library');
if (context != null && context != '')
buffer.write(', ');
} else {
buffer.writeln('Exception ');
}
if (context != null && context != '')
buffer.write('thrown $context');
buffer.writeln('.');
} else {
buffer.write('An error was caught.');
}
buffer.writeln(exceptionAsString());
if (informationCollector != null)
informationCollector(buffer);
if (stack != null) {
Iterable<String> stackLines = stack.toString().trimRight().split('\n');
if (stackFilter != null) {
stackLines = stackFilter(stackLines);
} else {
stackLines = FlutterError.defaultStackFilter(stackLines);
}
buffer.writeAll(stackLines, '\n');
}
return buffer.toString().trimRight();
}
}
/// Error class used to report Flutter-specific assertion failures and
......
......@@ -14,6 +14,7 @@ import 'framework.dart';
export 'package:flutter/animation.dart';
export 'package:flutter/foundation.dart' show
ChangeNotifier,
FlutterErrorDetails,
Listenable,
TargetPlatform,
ValueNotifier;
......
......@@ -840,13 +840,14 @@ class RenderObjectToWidgetElement<T extends RenderObject> extends RootRenderObje
_child = updateChild(_child, widget.child, _rootChildSlot);
assert(_child != null);
} catch (exception, stack) {
FlutterError.reportError(new FlutterErrorDetails(
final FlutterErrorDetails details = new FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'widgets library',
context: 'attaching to the render tree'
));
final Widget error = new ErrorWidget(exception);
);
FlutterError.reportError(details);
final Widget error = ErrorWidget.builder(details);
_child = updateChild(null, error, _rootChildSlot);
}
}
......
......@@ -3456,6 +3456,20 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
void performRebuild();
}
/// Signature for the constructor that is called when an error occurs while
/// building a widget.
///
/// The argument provides information regarding the cause of the error.
///
/// See also:
///
/// * [ErrorWidget.builder], which can be set to override the default
/// [ErrorWidget] builder.
/// * [FlutterError.reportError], which is typically called with the same
/// [FlutterErrorDetails] object immediately prior to [ErrorWidget.builder]
/// being called.
typedef Widget ErrorWidgetBuilder(FlutterErrorDetails details);
/// A widget that renders an exception's message.
///
/// This widget is used when a build method fails, to help with determining
......@@ -3467,6 +3481,32 @@ class ErrorWidget extends LeafRenderObjectWidget {
ErrorWidget(Object exception) : message = _stringify(exception),
super(key: new UniqueKey());
/// The configurable factory for [ErrorWidget].
///
/// When an error occurs while building a widget, the broken widget is
/// replaced by the widget returned by this function. By default, an
/// [ErrorWidget] is returned.
///
/// The system is typically in an unstable state when this function is called.
/// An exception has just been thrown in the middle of build (and possibly
/// layout), so surrounding widgets and render objects may be in a rather
/// fragile state. The framework itself (especially the [BuildOwner]) may also
/// be confused, and additional exceptions are quite likely to be thrown.
///
/// Because of this, it is highly recommended that the widget returned from
/// this function perform the least amount of work possible. A
/// [LeafRenderObjectWidget] is the best choice, especially one that
/// corresponds to a [RenderBox] that can handle the most absurd of incoming
/// constraints. The default constructor maps to a [RenderErrorBox].
///
/// See also:
///
/// * [FlutterError.onError], which is typically called with the same
/// [FlutterErrorDetails] object immediately prior to this callback being
/// invoked, and which can also be configured to control how errors are
/// reported.
static ErrorWidgetBuilder builder = (FlutterErrorDetails details) => new ErrorWidget(details.exception);
/// The message to display.
final String message;
......@@ -3544,8 +3584,7 @@ abstract class ComponentElement extends Element {
built = build();
debugWidgetBuilderValue(widget, built);
} catch (e, stack) {
_debugReportException('building $this', e, stack);
built = new ErrorWidget(e);
built = ErrorWidget.builder(_debugReportException('building $this', e, stack));
} finally {
// We delay marking the element as clean until after calling build() so
// that attempts to markNeedsBuild() during build() will be ignored.
......@@ -3556,8 +3595,7 @@ abstract class ComponentElement extends Element {
_child = updateChild(_child, built, slot);
assert(_child != null);
} catch (e, stack) {
_debugReportException('building $this', e, stack);
built = new ErrorWidget(e);
built = ErrorWidget.builder(_debugReportException('building $this', e, stack));
_child = updateChild(null, built, slot);
}
......@@ -4656,14 +4694,19 @@ class _DebugCreator {
String toString() => element.debugGetCreatorChain(12);
}
void _debugReportException(String context, dynamic exception, StackTrace stack, {
FlutterErrorDetails _debugReportException(
String context,
dynamic exception,
StackTrace stack, {
InformationCollector informationCollector
}) {
FlutterError.reportError(new FlutterErrorDetails(
final FlutterErrorDetails details = new FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'widgets library',
context: context,
informationCollector: informationCollector,
));
);
FlutterError.reportError(details);
return details;
}
......@@ -111,16 +111,14 @@ class _LayoutBuilderElement extends RenderObjectElement {
built = widget.builder(this, constraints);
debugWidgetBuilderValue(widget, built);
} catch (e, stack) {
_debugReportException('building $widget', e, stack);
built = new ErrorWidget(e);
built = ErrorWidget.builder(_debugReportException('building $widget', e, stack));
}
}
try {
_child = updateChild(_child, built, null);
assert(_child != null);
} catch (e, stack) {
_debugReportException('building $widget', e, stack);
built = new ErrorWidget(e);
built = ErrorWidget.builder(_debugReportException('building $widget', e, stack));
_child = updateChild(null, built, slot);
}
});
......@@ -225,11 +223,17 @@ class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin<Ren
}
}
void _debugReportException(String context, dynamic exception, StackTrace stack) {
FlutterError.reportError(new FlutterErrorDetails(
FlutterErrorDetails _debugReportException(
String context,
dynamic exception,
StackTrace stack,
) {
final FlutterErrorDetails details = new FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'widgets library',
context: context
));
);
FlutterError.reportError(details);
return details;
}
......@@ -16,7 +16,6 @@ void main() {
expect(log[1], contains('debugPrintStack'));
});
test('debugPrintStack', () {
final List<String> log = captureOutput(() {
final FlutterErrorDetails details = new FlutterErrorDetails(
......@@ -42,4 +41,55 @@ void main() {
expect(joined, contains('\nExample information\n'));
});
test('FlutterErrorDetails.toString', () {
expect(
new FlutterErrorDetails(
exception: 'MESSAGE',
library: 'LIBRARY',
context: 'CONTEXTING',
informationCollector: (StringBuffer information) {
information.writeln('INFO');
},
).toString(),
'Error caught by LIBRARY, thrown CONTEXTING.\n'
'MESSAGE\n'
'INFO',
);
expect(
new FlutterErrorDetails(
library: 'LIBRARY',
context: 'CONTEXTING',
informationCollector: (StringBuffer information) {
information.writeln('INFO');
},
).toString(),
'Error caught by LIBRARY, thrown CONTEXTING.\n'
' null\n'
'INFO',
);
expect(
new FlutterErrorDetails(
exception: 'MESSAGE',
context: 'CONTEXTING',
informationCollector: (StringBuffer information) {
information.writeln('INFO');
},
).toString(),
'Error caught by Flutter framework, thrown CONTEXTING.\n'
'MESSAGE\n'
'INFO',
);
expect(
const FlutterErrorDetails(
exception: 'MESSAGE',
).toString(),
'Error caught by Flutter framework.\n'
'MESSAGE'
);
expect(
const FlutterErrorDetails().toString(),
'Error caught by Flutter framework.\n'
' null'
);
});
}
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
void main() {
testWidgets('ErrorWidget.builder', (WidgetTester tester) async {
ErrorWidget.builder = (FlutterErrorDetails details) {
return const Text('oopsie!', textDirection: TextDirection.ltr);
};
await tester.pumpWidget(
new SizedBox(
child: new Builder(
builder: (BuildContext context) {
throw 'test';
},
),
),
);
expect(tester.takeException().toString(), 'test');
expect(find.text('oopsie!'), findsOneWidget);
});
}
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