0x00 前言
开发Flutter的时候,肯定都会遇到Flutter错误页面,可以让我们在开发的时候很清楚的知道代码有异常,但是,如果发布出去的APP出现了异常,那么这个错误页面就很不友好,其实这个错误页面是可以自定义的,本篇文章告诉你如何自定义错误页面!
0x01 Flutter错误页面
这是我们经常看到的错误页面:
0x02 自定义Flutter错误页面
要想Flutter的错误页面显示成自定义的页面,只要设置ErrorWidget
的builder
就行。 代码如下:
ErrorWidget.builder = (FlutterErrorDetails flutterErrorDetails){
print(flutterErrorDetails.toString());
return Center(
child: Text("Flutter 走神了"),
);
};
复制代码
ErrorWidget.builder
返回一个Widget,当Flutter出错的时候就会显示这个Widget, 下图就是我们自定义的错误页面,比Flutter的友好多了:
0x03 github
本篇文章所涉及的代码:
0x04 ErrorWidget源码分析
ErrorWidget
的源码在framework.dart
的3581行-3630行,很简单,ErrorWidget
的构造函数的参数是exception的对象,然后返回一个内容是exception message信息的RenderBox,我们看到的Flutter的错误页面就是这个RenderBox。
class ErrorWidget extends LeafRenderObjectWidget {
/// 创建一个显示error message的Widget,exception是构造函数的参数。
ErrorWidget(Object exception) : message = _stringify(exception),
super(key: UniqueKey());
//ErrorWidgetBuilder.builder的默认设置是ErrorWidget,我们可以设置成自己的
static ErrorWidgetBuilder builder = (FlutterErrorDetails details) => ErrorWidget(details.exception);
/// The message to display.
final String message;
//将exception对象转换成string
static String _stringify(Object exception) {
try {
return exception.toString();
} catch (e) { } // ignore: empty_catches
return 'Error';
}
//返回一个内容是exception message信息的RenderBox
@override
RenderBox createRenderObject(BuildContext context) => RenderErrorBox(message);
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(StringProperty('message', message, quoted: false));
}
}
复制代码
0x05 调用ErrorWidget的源码分析
前面看了ErrorWidget
的源码,只是创建一个Widget,那么是哪里调用ErrorWidget
显示的呢? 调用ErrorWidget
的代码总共有三处,这三处都有一个共同点,就是在build Widget的过程中,如果出现异常,则返回一个ErrorWidget显示,具体的源码如下:
@override
void performRebuild() {
assert(() {
if (debugProfileBuildsEnabled)
Timeline.startSync('${widget.runtimeType}', arguments: timelineWhitelistArguments);
return true;
}());
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
Widget built;
try {
built = build();
debugWidgetBuilderValue(widget, built);
} catch (e, stack) {
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.
_dirty = false;
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
}
try {
_child = updateChild(_child, built, slot);
assert(_child != null);
} catch (e, stack) {
built = ErrorWidget.builder(_debugReportException('building $this', e, stack));
_child = updateChild(null, built, slot);
}
assert(() {
if (debugProfileBuildsEnabled)
Timeline.finishSync();
return true;
}());
}
复制代码
- RenderObjectToWidgetElement(binding.dart)
void _rebuild() {
try {
_child = updateChild(_child, widget.child, _rootChildSlot);
assert(_child != null);
} catch (exception, stack) {
final FlutterErrorDetails details = FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'widgets library',
context: 'attaching to the render tree'
);
FlutterError.reportError(details);
final Widget error = ErrorWidget.builder(details);
_child = updateChild(null, error, _rootChildSlot);
}
}
复制代码
- _LayoutBuilderElement (layout_builder.dart)
void _layout(BoxConstraints constraints) {
owner.buildScope(this, () {
Widget built;
if (widget.builder != null) {
try {
built = widget.builder(this, constraints);
debugWidgetBuilderValue(widget, built);
} catch (e, stack) {
built = ErrorWidget.builder(_debugReportException('building $widget', e, stack));
}
}
try {
_child = updateChild(_child, built, null);
assert(_child != null);
} catch (e, stack) {
built = ErrorWidget.builder(_debugReportException('building $widget', e, stack));
_child = updateChild(null, built, slot);
}
});
}
复制代码