教你自定義Flutter錯誤頁面

0x00 前言

開發Flutter的時候,確定都會遇到Flutter錯誤頁面,能夠讓咱們在開發的時候很清楚的知道代碼有異常,可是,若是發佈出去的APP出現了異常,那麼這個錯誤頁面就很不友好,其實這個錯誤頁面是能夠自定義的,本篇文章告訴你如何自定義錯誤頁面!git

0x01 Flutter錯誤頁面

這是咱們常常看到的錯誤頁面: github

0x02 自定義Flutter錯誤頁面

要想Flutter的錯誤頁面顯示成自定義的頁面,只要設置ErrorWidgetbuilder就行。 代碼以下:bash

ErrorWidget.builder = (FlutterErrorDetails flutterErrorDetails){
        print(flutterErrorDetails.toString());
        return Center(
          child: Text("Flutter 走神了"),
          );
    };
複製代碼

ErrorWidget.builder返回一個Widget,當Flutter出錯的時候就會顯示這個Widget, 下圖就是咱們自定義的錯誤頁面,比Flutter的友好多了: ide

0x03 github

本篇文章所涉及的代碼: github.com/koudle/GDG_…函數

github地址:github.com/koudle/GDG_…源碼分析

0x04 ErrorWidget源碼分析

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));
  }
}
複製代碼

0x05 調用ErrorWidget的源碼分析

前面看了ErrorWidget的源碼,只是建立一個Widget,那麼是哪裏調用ErrorWidget顯示的呢? 調用ErrorWidget的代碼總共有三處,這三處都有一個共同點,就是在build Widget的過程當中,若是出現異常,則返回一個ErrorWidget顯示,具體的源碼以下:this

  1. ComponentElement(framework.dart)
@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);
      }
    });
  }
複製代碼
相關文章
相關標籤/搜索