mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Allow the ErrorWidget to be overridden. (#13578)
Fixes https://github.com/flutter/flutter/issues/10695
This commit is contained in:
parent
f9cf5a1f3d
commit
c28121eeca
@ -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'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
25
packages/flutter/test/widgets/error_widget_builder_test.dart
Normal file
25
packages/flutter/test/widgets/error_widget_builder_test.dart
Normal file
@ -0,0 +1,25 @@
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user