Способ реализации обработки ошибок в вашем приложении Flutter.

Давайте подробнее рассмотрим Обработчик ошибок, впервые представленный в конце предыдущей статьи Обработка ошибок во Flutter. Обратите внимание: сначала рекомендуется прочитать эту предыдущую статью, чтобы быстрее освоиться. Этот класс позволяет вам вводить собственный обработчик ошибок либо для вашего приложения в целом, либо только для сегментов вашего кода. По общему признанию, такая возможность может быть полезна только для более крупного приложения с участием нескольких разработчиков, но мы надеемся, что этот класс по крайней мере даст вам некоторые идеи по теме обработки ошибок.

В этой небольшой статье мы пройдемся по частям класса, объясняя роль, которую они играют в обеспечении обработки ошибок. Вам будет представлен образец кода, демонстрирующий, как реализован класс. Конечно, класс ErrorHandler доступен вам на Github. Возьми и сделай своим. Обработка ошибок необходима в каждом приложении. Во всяком случае, может быть, эта статья заставит вас задуматься об обработке ошибок. Сделай это! Обработайте свои ошибки. Они произойдут!

Давайте начнем.

В начале класса, ErrorHandler, вы можете увидеть как исходные процедуры «обработчика ошибок», так и процедуры «ErrorWidget», которые в настоящее время вызываются при возникновении ошибки и записываются в частных переменных пакета в классе. конструктор. Напоминаем, что ErrorWidget - это замечательный «красный экран судьбы», который появляется, когда во время разработки происходит что-то плохое.

class ErrorHandler {
  //
  ErrorHandler({
    FlutterExceptionHandler handler,
    ErrorWidgetBuilder builder,
  }) {
    _oldOnError = FlutterError.onError;
    _oldBuilder = ErrorWidget.builder;

    set(builder: builder, handler: handler);
  }

Если два параметра, handler и builder, переданные в конструктор, являются «именованными параметрами», это означает, что у вас есть возможность предоставить свой собственный обработчик ошибок и «error widget 'прямо при первом создании экземпляра объекта ErrorHandler. Это примечательно. При этом сразу же определяются подпрограммы «обработчик ошибок» и «ErrorWidget», которые будут использоваться вместо этого на этом этапе.

Эти названные параметры, конечно же, подразумевают, что у вас есть другие средства для назначения этих параметров. Фактически, вы можете увидеть еще один вариант прямо внутри самого конструктора. Есть общедоступная функция set (). Вместо этого вы можете использовать эту функцию - у нее тоже есть два именованных параметра. Вы можете назначить любому из них эту функцию. У тебя есть такая возможность.

Далее в коде есть функция dispose (). Как вы могли догадаться, эта функция восстанавливает исходный «обработчик» и «построитель», когда вы «выходите» из сегмента кода, используя этот конкретный обработчик ошибок и / или виджет ошибок.

void dispose() {
  // Restore the error widget routine.
  if (_oldBuilder != null) ErrorWidget.builder = _oldBuilder;
  // Return the original error routine.
  if (_oldOnError != null) FlutterError.onError = _oldOnError;
}

Итак, почему вам разрешено создавать более одного обработчика ошибок? Что ж, у вас есть варианты. Если вы меня знаете, вы знаете, что я люблю варианты. Например, каждый объект State в приложении Flutter представляет пользователю интерфейс. Во многих случаях большая часть кода для этого интерфейса и для этой конкретной части приложения находится в этом самом объекте State. Следовательно, могут быть случаи, когда вы хотите, чтобы конкретный объект State имел собственный обработчик ошибок. Это могло случиться?!

По крайней мере, с этим классом «обработчика ошибок» у вас есть такая возможность. Вот пример ниже. Вы можете увидеть, что обработчик ошибок создается в функции initState () объекта State. Обратите внимание: когда объект State завершается, функция обработчика ошибок dispose () также вызывается в собственной функции dispose () объекта State - для восстановления исходной ошибки. умение обращаться. Видишь, как это работает? Очень модульный. Это вариант.

class _DetailsScreenState extends State<DetailsScreen> {
  int _selectedViewIndex = 0;

  @override
  void initState(){
    super.initState();
    handler = ErrorHandler.init();
  }
  ErrorHandler handler;

  @override
  void dispose(){
    handler.dispose();
    super.dispose();
  }

Вы могли заметить, что в приведенном выше примере именованный конструктор ErrorHandler. init () использовался вместо более часто используемого генеративного конструктора. Оказывается, как вы видите ниже, этот конструктор на самом деле также вызывает исходный конструктор, но явно предоставляет «Виджет ошибок» для именованного параметра builder. Это версия по умолчанию, которую вы можете использовать в виде частной функции пакета _defaultErrorWidget (). Он просто отображает светло-серый экран в случае сбоя, а не красный. Это все.

ErrorHandler.init() {
    /// Assigns a default ErrorWidget.
    ErrorHandler(
        builder: (FlutterErrorDetails details) => _defaultErrorWidget(details));
  }

Конечно, вы можете использовать исходный конструктор и назначить свой собственный ErrorWidget. Черт возьми! У вас есть копия самого класса - измените функцию, _defaultErrorWidget! Эта копия предназначена просто для демонстрации того, что можно сделать для реализации обработки ошибок в вашем приложении. Сделай сам!

Запустите приложение для обработки ошибок

В классе есть один статический член, ErrorHandler, который может оказаться полезным. Это статическая функция, которая определяет обработчик ошибок для всего приложения. Вы узнаете название этой функции. Он называется runApp ().

void main() => ErrorHandler.runApp(MyApp());

В этой статической функции много чего происходит. Это гарантирует, что любой «вид ошибки» будет обнаружен указанным обработчиком ошибок. Кроме того, у вас есть возможность передать созданную версию этого самого класса в качестве объекта. Это делается для того, чтобы назначить свои собственные процедуры «обработки ошибок» всему приложению. Я покажу вам, как это делается в ближайшее время. А пока давайте взглянем на функцию runApp ().

/// Wrap the Flutter app in the Error Handler.
static void runApp(Widget myApp, [ErrorHandler handler]) {
  // Can't be used properly if being called again.
  if (ranApp) return;

  ranApp = true;
  // Catch any errors in Flutter.
  handler ??= ErrorHandler();

  // Catch any errors in the main() function.
  Isolate.current.addErrorListener(new RawReceivePort((dynamic pair) async {
    var isolateError = pair as List<dynamic>;
    _reportError(
      isolateError.first.toString(),
      isolateError.last.toString(),
    );
  }).sendPort);

  // To catch any 'Dart' errors 'outside' of the Flutter framework.
  runZoned<Future<void>>(() async {
    w.runApp(myApp);
    // Catch any errors in the error handling.
  }, onError: (error, stackTrace) {
    _reportError(error, stackTrace);
  });
}

В этой статической функции вашему приложению Flutter представлены три «обратных вызова обработчика ошибок». Первый определяет обработчик исключений Flutter.onError и «виджет ошибок» ErrorWidget.builder. Это включает параметр, обработчик . Если параметр не передан, переменной handler назначается экземпляр объекта. Это определит обработчик ошибок «по умолчанию» и процедуру «ErrorWidget», которые будут использоваться на этом этапе.

// Catch any errors in Flutter.
handler ??= ErrorHandler();

Второй обратный вызов обеспечивает обработку ошибок для любого кода, который может вызвать ошибку в самой функции ввода main (). Это включает в себя Изоляторы и Зоны и выходит за рамки данной статьи (вне меня, если на то пошло), но, насколько я понимаю в настоящее время, это именно то, что он делает.

Isolate.current.addErrorListener(new RawReceivePort((dynamic pair) async {
    var isolateError = pair as List<dynamic>;
    _reportError(
      isolateError.first.toString(),
      isolateError.last.toString(),
    );
  }).sendPort);

Последний обратный вызов позволяет вам обнаруживать любые ошибки «вне» фреймворка Flutter - например, любые ошибки Dart в самом обработчике исключений Flutter. Для этого вместо запуска приложения Flutter в «корневой зоне» - подобно функции входа main (), создается «дочерняя» зона для запуска приложения Flutter. Опять же, насколько я сейчас понимаю, зона - это запланированная задача памяти; отдельный процесс, который работает без перерывов от начала до конца. Тем не менее, любые ошибки, не охваченные обработчиком исключений Flutter, FlutterError.onError, перехватываются предложением onError, показанным ниже.

// To catch any 'Dart' errors 'outside' of the Flutter framework.
  runZoned<Future<void>>(() async {
    w.runApp(myApp);
    // Catch any errors in the error handling.
  }, onError: (error, stackTrace) {
    _reportError(error, stackTrace);
  });

Обратите внимание, что все три обратных вызова вызывают одну и ту же процедуру под названием _reportError (). Эта процедура принимает два параметра, объект ошибка и объект трассировка стека, и создает объект FlutterErrorDetails. Это тот объект, который передается статической функции FlutterError.reportError ().

/// Produce the FlutterErrorDetails object and call Error routine.
static FlutterErrorDetails _reportError(
  dynamic exception,
  dynamic stack,
) {
  final FlutterErrorDetails details = FlutterErrorDetails(
    exception: exception,
    stack: stack is String ? StackTrace.fromString(stack) : stack,
    library: 'error_handler.dart',
  );

  // Call the Flutter 'onError' routine.
  FlutterError.reportError(details);
  // Returned to the ErrorWidget object in many cases.
  return details;
}

Глубоко во фреймворке Flutter в файле assertions.dart вы можете увидеть, что эта функция, в свою очередь, делает - она ​​вызывает статическую функцию FlutterExceptionHandler, onError, если таковая имеется. Видите это если? Это говорит о том, что вы можете присвоить FlutterError.onError значение null и игнорировать любые ошибки Flutter. Не делай этого. Конечно, обработчик исключений Flutter назначается обратно в функции класса ErrorHandler, set (). Помните?

/// Calls [onError] with the given details, unless it is null.
static void reportError(FlutterErrorDetails details) {
  assert(details != null);
  assert(details.exception != null);
  if (onError != null)
    onError(details);
}

Передайте своего обработчика

Опять же, у вас есть возможность передать объект ErrorHandler статической функции ErrorHandler.runApp (), что позволит вам указать свой собственный «обработчик ошибок» и «виджет ошибок». Некоторые примеры кода демонстрируют это ниже.

void main() {

  ErrorHandler handler = ErrorHandler();
  
  handler.onError = (FlutterErrorDetails details) => yourErrorHandler();
  
  handler.builder = (FlutterErrorDetails details)=> yourErrorWidget();
  
  return ErrorHandler.runApp(MyApp(), handler);
}

Давайте наконец взглянем на эту функцию set (). Опять же, именно в этой функции, если функция «виджета ошибки» передается указанному параметру, builder, затем она назначается ErrorWidget.builder. Если подпрограмма обработчика исключений передается в эту функцию с использованием именованного параметра handler, она затем присваивается частной переменной пакета с именем _onError. Вы можете видеть, что функция set () имеет собственную процедуру обработки исключений, фактически назначенную статической функции Flutter.onError . В этой подпрограмме , в конечном итоге вызывается переменная _onError. Обратите внимание: если переменная _onError имеет значение null, вместо этого вызывается исходный обработчик исключений (хранящийся в переменной _oldOnError). Вы так далеко следите? Попробуйте его в своем любимом отладчике и следите за его выполнением. Вы получите это.

void set({
  Widget Function(FlutterErrorDetails details) builder,
  void Function(FlutterErrorDetails details) handler,
}) {
  //
  if (builder != null) ErrorWidget.builder = builder;

  if (handler != null) _onError = handler;

  FlutterError.onError = (FlutterErrorDetails details) {
    // Prevent an infinite loop and fall back to the original handler.
    if (_inHandler) {
      if (_onError != null && _oldOnError != null) {
        _onError = null;
        try {
          _oldOnError(details);
        } catch (ex) {
          // intentionally left empty.
        }
      }
      return;
    }

    // If there's an error in the error handler, we want to know about it.
    _inHandler = true;

    final FlutterExceptionHandler handler =
        _onError == null ? _oldOnError : _onError;

    if (handler != null) {
      handler(details);
      _inHandler = false;
    }
  };
}

Установите своих обработчиков

Кстати, в классе также есть два сеттера. Это позволяет вам другим способом назначить вашему приложению процедуру обработчика ошибок или процедуру «виджета ошибок». Ниже вы можете видеть, что оба установщика вызывают нашего старого друга, функцию set (). Каждый просто использует параметр с соответствующим именем. Обратите внимание, что явное присвоение нулевого значения вернет каждый к исходной подпрограмме. Почему? Так что у вас есть такая возможность. Вот почему.

/// Set to null to use the 'old' handler.
set onError(FlutterExceptionHandler handler) {
  // So you can assign null and use the original error routine.
  _onError = handler;
  set(handler: handler);
}

/// Set the ErrorWidget.builder
/// If assigned null, use the 'old' builder.
set builder(ErrorWidgetBuilder builder) {
  if (builder == null) builder = _oldBuilder;
  set(builder: builder);
}

Наконец, в конце класса ErrorHandler перечислена скромная, по умолчанию небольшая процедура виджета ошибок. По общему признанию, здесь не на что смотреть. Однако возьмите копию и измените ее. Сделать его лучше. Сделайте это по-своему.

Widget _defaultErrorWidget(FlutterErrorDetails details) {
  String message;
  try {
    message = "ERROR\n\n" + details.exception.toString() + "\n\n";

    List<String> stackTrace = details.stack.toString().split("\n");

    int length = stackTrace.length > 5 ? 5 : stackTrace.length;

    for (var i = 0; i < length; i++) {
      message += stackTrace[i] + "\n";
    }
  } catch (e) {
    message = 'Error';
  }

  final Object exception = details.exception;
  return _WidgetError(
      message: message, error: exception is FlutterError ? exception : null);
}

Я оставлю все как есть. Опять же, это небольшое упражнение было просто дать вам некоторый код для работы. Возьмите его и подумайте, как вы хотите справиться с этими неизбежными ошибками в собственном приложении. Ваши пользователи будут вечно благодарны.

Ваше здоровье.

→ Другие рассказы Грега Перри