您的当前位置:首页正文

教你自定义Flutter错误页面

2024-11-29 来源:个人技术集锦

0x00 前言

开发Flutter的时候,肯定都会遇到Flutter错误页面,可以让我们在开发的时候很清楚的知道代码有异常,但是,如果发布出去的APP出现了异常,那么这个错误页面就很不友好,其实这个错误页面是可以自定义的,本篇文章告诉你如何自定义错误页面!

0x01 Flutter错误页面

这是我们经常看到的错误页面:

0x02 自定义Flutter错误页面

要想Flutter的错误页面显示成自定义的页面,只要设置ErrorWidgetbuilder就行。 代码如下:

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;
    }());
  }
复制代码
  1. 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);
    }
  }
复制代码
  1. _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);
      }
    });
  }
复制代码
显示全文