開發Flutter的時候,確定都會遇到Flutter錯誤頁面,能夠讓咱們在開發的時候很清楚的知道代碼有異常,可是,若是發佈出去的APP出現了異常,那麼這個錯誤頁面就很不友好,其實這個錯誤頁面是能夠自定義的,本篇文章告訴你如何自定義錯誤頁面!git
這是咱們常常看到的錯誤頁面: github
要想Flutter的錯誤頁面顯示成自定義的頁面,只要設置ErrorWidget
的builder
就行。 代碼以下:bash
ErrorWidget.builder = (FlutterErrorDetails flutterErrorDetails){
print(flutterErrorDetails.toString());
return Center(
child: Text("Flutter 走神了"),
);
};
複製代碼
ErrorWidget.builder
返回一個Widget,當Flutter出錯的時候就會顯示這個Widget, 下圖就是咱們自定義的錯誤頁面,比Flutter的友好多了: ide
本篇文章所涉及的代碼: github.com/koudle/GDG_…函數
github地址:github.com/koudle/GDG_…源碼分析
ErrorWidget
的源碼在framework.dart
的3581行-3630行,很簡單,ErrorWidget
的構造函數的參數是exception的對象,而後返回一個內容是exception message信息的RenderBox,咱們看到的Flutter的錯誤頁面就是這個RenderBox。ui
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));
}
}
複製代碼
前面看了ErrorWidget
的源碼,只是建立一個Widget,那麼是哪裏調用ErrorWidget
顯示的呢? 調用ErrorWidget
的代碼總共有三處,這三處都有一個共同點,就是在build Widget的過程當中,若是出現異常,則返回一個ErrorWidget顯示,具體的源碼以下:this
@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;
}());
}
複製代碼
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);
}
}
複製代碼
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);
}
});
}
複製代碼