Как выполнять обработку ошибок с блочным шаблоном во флаттер?

Представьте, что я использую блок для обработки сетевого запроса. В случае сбоя запроса способ обработки сбоя будет различным в зависимости от платформы. В моем веб-приложении я хотел бы перенаправить пользователя на страницу с ошибкой, в то время как в моем приложении IOS я хотел бы показать диалоговое окно.

Поскольку блок должен использоваться и использоваться совместно только для обработки бизнес-логики, а часть обработки ошибок не имеет ничего общего с бизнес-логикой, мы должны попросить часть пользовательского интерфейса позаботиться об обработке ошибок.

Пользовательский интерфейс может отправить обратный вызов ошибки в блок, и блок будет запускать его при возникновении ошибки. Мы также можем обработать ошибку в зависимости от платформы, отправив различные обратные вызовы на разных платформах.

Затем идут два моих вопроса:

Есть ли более подходящий способ сделать это?

Как отправить обратный звонок в блок?

Во флаттере мы имеем доступ к блоку только после метода жизненного цикла initState (поскольку мы получаем блок из контекста компоновщика, который следует только после initState). Тогда мы можем только отправить обратный вызов в методе сборки.

Таким образом, мы будем повторно отправлять обратный вызов в блок каждый раз, когда происходит восстановление (эти повторения не имеют смысла).   С помощью реакции такая одноразовая инициализация может быть выполнена в таких жизненных циклах, как componentDidMount. В трепетании, как мы достигаем цели запуска этих инициализаций только один раз?

Ответ 1

Вот как мы справляемся с этим в моей команде:

Сначала мы создаем нашу главную страницу (корень навигации) следующим образом:

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<SuspectEvent, SuspectState>(
        bloc: _bloc,
        builder: (context, state) {
          if (state.cameras.isEmpty) _bloc.dispatch(GetCamerasEvent());

          if (!_isExceptionHandled) {
            _shouldHandleException(
                hasException: state.hasException,
                handleException: state.handleException);
          }
        return Scaffold(
   ...

Мы объявляем _shouldHandleException следующим образом (все еще на главной странице):

  _shouldHandleException(
      {@required bool hasException, @required Exception handleException}) {
    if (hasException) {
      if (handleException is AuthenticationException) {
        _isExceptionHandled = true;
        SchedulerBinding.instance.addPostFrameCallback((_) async {
          InfoDialog.showMessage(
                  context: context,
                  infoDialogType: DialogType.error,
                  text: 'Please, do your login again.',
                  title: 'Session expired')
              .then((val) {
            Navigator.popUntil(context, ModalRoute.withName('/'));
            this._showLogin();
          });
        });
      } else if (handleException is BusinessException) {
        _isExceptionHandled = true;
        SchedulerBinding.instance.addPostFrameCallback((_) async {
          InfoDialog.showMessage(
                  context: context,
                  infoDialogType: DialogType.alert,
                  text: handleException.toString(),
                  title: 'Verify your fields')
              .then((val) {
            _bloc.dispatch(CleanExceptionEvent());
            _isExceptionHandled = false;
          });
        });
      } else {
        _isExceptionHandled = true;
        SchedulerBinding.instance.addPostFrameCallback((_) async {
          InfoDialog.showMessage(
                  context: context,
                  infoDialogType: DialogType.error,
                  text: handleException.toString(),
                  title: 'Error on request')
              .then((val) {
            _bloc.dispatch(CleanExceptionEvent());
            _isExceptionHandled = false;
          });
        });
      }
    }
  }

На нашем блоке у нас есть:


  @override
  Stream<SuspectState> mapEventToState(SuspectEvent event) async* {
    try {
      if (event is GetCamerasEvent) {

        ... //(our logic)
        yield (SuspectState.newValue(state: currentState)
          ..cameras = _cameras
          ..suspects = _suspects);
      }
      ... //(other events)
    } catch (error) {
      yield (SuspectState.newValue(state: currentState)
        ..hasException = true
        ..handleException = error);
    }
  }

В нашей обработке ошибок (на главной странице) InfoDialog - это просто showDialog (от Flutter), и он попадает в showDialog любого маршрута. Так что оповещение просто необходимо вызвать на корневом маршруте.

Ответ 2

Для обработки распространенных ошибок, таких как сеть, время ожидания и т.д., Используйте BlocDelegate