web-dev-qa-db-fra.com

Comment faire la gestion des erreurs avec le motif de bloc en flutter?

Imaginez que j'utilise un bloc pour gérer une demande de réseau. Si la demande échoue, la façon de gérer l'échec serait différente selon la plate-forme. Sur mon application web, je voudrais rediriger l'utilisateur vers une page d'erreur tandis que sur mon application IOS je voudrais afficher une boîte de dialogue.

Étant donné que le bloc ne doit être utilisé et partagé que pour gérer la logique métier, et que la partie de gestion des erreurs n'a rien à voir avec la logique métier, nous devons demander à la partie UI de prendre soin de la gestion des erreurs.

L'interface utilisateur peut envoyer un rappel d'erreur au bloc et le bloc l'exécutera lorsqu'une erreur se produit. Nous pouvons également gérer l'erreur d'une manière spécifique à la plate-forme en envoyant différents rappels sur différentes plates-formes.

Viennent ensuite mes deux questions:

Existe-t-il une manière plus appropriée de procéder?

Comment envoyer le rappel au bloc?

En flutter, nous n'avons accès au bloc qu'après la méthode du cycle de vie initState (car nous obtenons le bloc à partir du contexte du générateur, qui ne vient qu'après initState). Ensuite, nous ne pouvons envoyer de rappel que dans la méthode de construction.

De cette façon, nous enverrons des rappels répétitifs au bloc chaque fois que la reconstruction se produit (ces répétitions n'ont aucun sens). Avec react, une telle initialisation unique pourrait être effectuée dans des cycles de vie tels que componentDidMount. En flutter, comment pouvons-nous atteindre l'objectif de lancer ces initialisations une seule fois?

18
lo__tp

Voici comment nous le gérons dans mon équipe:

Nous construisons d'abord notre page principale (la racine de navigation) comme ceci:

  @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(
   ...

Nous déclarons le _shouldHandleException comme ça (toujours sur la page principale):

  _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;
          });
        });
      }
    }
  }

Sur notre bloc, nous avons:


  @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);
    }
  }

Dans notre gestion des erreurs (sur la page principale), le InfoDialog est juste un showDialog (de Flutter) et il arrive au-dessus de n'importe quelle route. Il fallait donc appeler l'alerte sur la route racine.

1
LgFranco

Vous pouvez accéder au BLoC dans la méthode initState si vous l'encapsulez dans une méthode scheduleMicrotask, afin qu'il s'exécute une fois la méthode initState terminée:

@override
void initState() {
  super.initState();
  // Do initialization here.
  scheduleMicrotask(() {
    // Do stuff that uses the BLoC here.
  });
}

Vous pouvez également consulter cette réponse à une autre question décrivant le modèle BLoC simple, qui appelle simplement des méthodes asynchrones directement sur le BLoC au lieu de mettre des événements dans des récepteurs.

Cela permettrait un code comme celui-ci:

Future<void> login() {
  try {
    // Do the network stuff, like logging the user in or whatever.
    Bloc.of(context).login(userController.text, emailController.text);
  } on ServerNotReachableException {
    // Redirect the user, display a Prompt or change this
    // widget's state to display an error. It's up to you.
  }
}
0
Marcel