Асинхронная обработка исключений (Firebase) с пакетом Provider (Flutter)

1. Проблема

Я использую динамический try-catch-finally 1 для обработки исключений для Firebase в моем простом пакете аутентификации для моего приложения. В нем, когда я получаю сообщение об ошибке от службы аутентификации Firebase, я обновляю labelText в (настраиваемом) TextFormField, чтобы показать пользователю, что произошла ошибка.

Однако notifyListeners(), очевидно, выполняется одновременно с предложением catch, из-за чего перестройки не происходят синхронно с обработкой исключений.

В настоящее время я использую ChangeNotifierProvider практически для всего, но должен ли я перейти на FutureProvider динамический. Если да, то как лучше всего это сделать?


1 Я тоже пробовал использовать then-catchError-whenComplete.

2. Кодекс

2.1 Полный код (необязательно)

Полный код на этом этапе проекта немного длинен, но я думаю, что показанного ниже будет достаточно.

В любом случае, если вы хотите все проверить, доступен весь проект: flutter_firebase_auth_benchmark.

Соответствующие файлы:

  • firebase_auth.dart: класс provider с соответствующими данными.
  • login_screen.dart: email_field получает данные provider с Consumer виджетом.
  • password_reset_workflow.dart: кнопка, вызывающая соответствующий метод, помещается в LoginScreen через AnimatedSwitcher.

2.2 Класс провайдера

Мне приходится использовать Future.delayed, чтобы вручную синхронизировать методы notifyListeners() и build() - я знаю, знаю, это очень плохо ...

class Auth extends ChangeNotifier {
  String _errorMsg;

  String get errorMsg => _errorMsg;

  ...

  Future<void> sendPasswordResetWithEmail({@required String email}) async {
    try {
      await FirebaseAuth.instance.sendPasswordResetEmail(email: email);
    } catch(e) {
      switch (e.code) {
        case 'ERROR_USER_NOT_FOUND':
          _errorMsg = 'user not found';
          break;
        default:
          throw UnknownPasswordResetError(
              'Unknown error for password reset with Firebase.');
      }
    } finally {
      notifyListeners();
      await Future.delayed(Duration(milliseconds: 10));
    }
  }
}

2.3 Мой пользовательский TextFormField виджет

Примерно так есть в приложении:

return Consumer<Auth>(
  builder: (context, auth, _) {
    return AuthTextFormField(
      ...,
      errorMsgFromServer: auth.errorMsg,
    );
  }
);

Наконец, кнопка проверки использует formKey.currentState.validate() в предложении if для асинхронного запуска await auth.sendPasswordResetWithEmail.


person Philippe Fanaro    schedule 19.03.2020    source источник
comment
До сих пор я был ближе всего к ответу на эту проблему, связанный с этим SO-ответом.   -  person Philippe Fanaro    schedule 20.03.2020


Ответы (1)


notifyListeners (), по-видимому, запускается одновременно с предложением catch

notifyListeners() находится внутри блока finally, finally будет выполняться ВСЕГДА, независимо от того, произошла ли у вас ошибка, поэтому всегда вызывается notifyListeners().

Кроме того, использовать Future.delayed для синхронизации событий очень плохо, вам следует сделать для этого другую архитектуру.

person Michel Feinstein    schedule 19.03.2020
comment
Я знаю, что notifyListeners() всегда будет выполняться, но я бы хотел, чтобы он выполнялся после обработки ошибок, а не после завершения предложения try. И я также знаю, что использование Future.delayed для синхронизации событий - это очень плохо, вероятно, это и есть причина для публикации вопроса. - person Philippe Fanaro; 20.03.2020
comment
Что вы имеете в виду после обработки ошибки, а не после try? Извините, но я не понимаю ваш английский - person Michel Feinstein; 20.03.2020
comment
Fala em português que depois eu edito a resposta em inglês pra todo mundo entender. - person Michel Feinstein; 20.03.2020
comment
Из множества вариантов кажется, что последовательность, в которой выполняется код, следующая: trycatch параллельно с finally. Итак, notifyListeners() будет вызываться во время обновления errorMsg. Вызов Future.delayed заставит все синхронизировать вручную. Но я не уверен во всей последовательности, это только то, что я смог догадаться во время отладки. - person Philippe Fanaro; 20.03.2020
comment
desculpa já tinha escrito em inglês antes de ver a sua última mensagem. - person Philippe Fanaro; 20.03.2020
comment
Nao existe paralelo em Dart, tudo roda em serie. Quando voce atualiza _errorMsg voce Precisa mandar o Flutter dar rebuild na tua tela pra essa nova informacao aparecer, voce tem que ver quem ta gerando esse rebuild pra ver qual o проблема. Geralmente o notifyListeners() faz o rebuild automaticamente entao voce Precisa dele pra que a informacao apareca. - person Michel Feinstein; 20.03.2020
comment
Voce ta fornecendo o Auth com um ChangeNotifierProvider<Auth> ne - person Michel Feinstein; 20.03.2020
comment
Сим, я тебя люблю Auth com um ChangeNotifierProvider<Auth>. Como eu mencionei, a arquitetura está ficando gradient mais Compassada, então fica diffícil de eu fazer um resumo aqui. Mas acho que a maior dificuldade no memomento é que a criação do campo fica em uma tela e o botão que atualiza o labelText aparece através de um AnimatedSwitcher. Поиск по умам Ольхады: flutter_firebase_auth_benchmark. - person Philippe Fanaro; 20.03.2020
comment
Qual o arquivo com проблема? - person Michel Feinstein; 20.03.2020
comment
O campo email_field na login_screen.dart é o que deveria sofrer alterações via provider. О arquivo firebase_auth.dart é o que Possui a class do provider. E o arquivo password_reset_workflow.dart é o que possibleui o botão que efetua a atualização (notifyListeneres()). - person Philippe Fanaro; 20.03.2020
comment
Você tem que criar um provider com o Type da classe, tem que ser ChangeNotifierProvider<Auth>(create: - person Michel Feinstein; 20.03.2020
comment
Tentei isso, mas já achava que não ia ajudar de qualquer maneira, pois o Dart está capturando implicitamente o tipo do ChangeNotifierProvider quando ele compila o código. - person Philippe Fanaro; 20.03.2020
comment
Eu tentei rodar o teu codigo, mas nao poso pq nao tenho o json do firebase, entao so Posso chutar qual é o проблема. Eu acho que voce deve mudar a sua arquitetura e fazer tudo virar um Future que so retorna quando ta tudo pronto e perfeito. - person Michel Feinstein; 22.03.2020
comment
Eu acho que descobri qual é o problem, mas não tenho 100% de certeza. Провайдеры Eu tenho dois, um que cuida do Form e do gerenciamento do AnimatedSwitcher (LoginWorkflowHandler) e o outro que cuida das transações com o Firebase (Auth). O проблема, acho, é que, quando eu aperto o botão pra mandar um pedido de nova senha, por exemplo, o provider Auth pede pra atualizar a árvore e, logo em seguida, no mesmo onPressed, o LoginWorkflowProvider também pede para que uma atualizaço ocorra, mas através do save() do Form. Acho que a concorrência entre eles está causando o проблема. - person Philippe Fanaro; 22.03.2020
comment
Eu acho que voiceê ta abusando um pouco dessa arquitetura ... Você pode simpleificar as coisas com FutureBuilder, StreamBuilder etc e nem Precisar de um ChangeNotifier - person Michel Feinstein; 22.03.2020
comment
Сим, конкордо. Mas é que eu acabei descobrindo esses issuesas só nos últimos dias ... E agora estou meio que sem paciência pra mudar a arquitetura. - person Philippe Fanaro; 22.03.2020