教你自定義Flutter錯誤頁面

小德_REN發表於2019-01-16

0x00 前言

開發Flutter的時候,肯定都會遇到Flutter錯誤頁面,可以讓我們在開發的時候很清楚的知道程式碼有異常,但是,如果釋出出去的APP出現了異常,那麼這個錯誤頁面就很不友好,其實這個錯誤頁面是可以自定義的,本篇文章告訴你如何自定義錯誤頁面!

0x01 Flutter錯誤頁面

這是我們經常看到的錯誤頁面:

教你自定義Flutter錯誤頁面

0x02 自定義Flutter錯誤頁面

要想Flutter的錯誤頁面顯示成自定義的頁面,只要設定ErrorWidgetbuilder就行。 程式碼如下:

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

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

教你自定義Flutter錯誤頁面

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。

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顯示,具體的原始碼如下:

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

相關文章