40
votes

Bloc, scintillement et navigation

Donc, comme la plupart des gens, je suis nouveau dans Bloc et je flutter et dard et enveloppant ma tête. J'ai cherché sur Google, parcouru les messages ici mais je n'ai trouvé aucune réponse.

Il s'agit donc de navigation avec bloc et flutter. Prenons l'exemple d'une connexion. Il y a donc une page de connexion avec un bloc derrière et à un moment donné, quelqu'un appuie sur un bouton pour se connecter.

Nous pouvons donc appeler une fonction dans le bloc qui effectue la validation. Je pense que cela va à l'encontre de l'approche stricte, mais je vois des gens faire cela. Mais si la connexion est réussie, comment accédez-vous à l'écran suivant? Vous n'êtes pas censé naviguer en bloc?

Mais si cette page de connexion utilise un StreamBuilder pour changer d'état, vous ne pouvez pas non plus ajouter de navigation dans un générateur, n'est-ce pas? Vous ne pouvez pas retourner la navigation, vous renvoyez des widgets.

L'initstate est quelque part dans lequel vous pouvez naviguer, mais pouvez-vous avoir un générateur de flux dans un initstate qui écoute les changements d'état dans le bloc?

Tout est un peu déroutant pour le moment, mais je persévère car c'est censé être la voie à suivre ...

merci Paul


6 commentaires

Voir stackoverflow.com/questions/54101589/...


Merci Remi, je ne suis pas en mesure de commenter ce message, mais je me suis concentré sur ce sujet - nous devons donc utiliser des widgets avec état? J'ai toujours pensé que bloc s'était éloigné des widgets avec état. Ensuite, le bloc détermine toujours quand naviguer en passant des valeurs via le Stream. Cela semble un peu maladroit, il faut travailler un peu plus! Merci!


BLoC ne supprime en aucun cas le besoin de StatefulWidget


Idéalement, vous devriez pouvoir écouter le BLoC.stream dans initState. Mais je suis confronté à un autre problème avec cette approche: la méthode recommandée pour fournir un BLoC consiste à utiliser un fournisseur basé sur Inherited Widget en utilisant un appel à inheritFromWidgetOfExactType. Le problème ici est que vous ne pouvez pas appeler inheritFromWidgetOfExactType dans initState, uniquement dans didChangeDependencies. Mais didChangeDependencies est appelé par le framework sur diverses instances, y compris lorsque vous quittez une page. Vous pouvez donc entrer dans une boucle: la navigation déclenche le changement des dépendances et vice versa.


Voir cette question pour un exemple


Voir stackoverflow.com/a/59347530/7158449


4 Réponses :


37
votes

Pour éliminer le mythe du BLoC étant la voie à suivre: il n'y a pas de moyen parfait pour gérer l'état. Chaque architecture de gestion d'état résout certains problèmes mieux que d'autres; il y a toujours des compromis et il est important d'en être conscient lors du choix d'une architecture.

En général, une bonne architecture est pratique : elle est évolutive et extensible tout en ne nécessitant qu'une surcharge minimale. Parce que les opinions des gens sur la praticabilité diffèrent, l'architecture implique toujours une opinion, alors prenez ce qui suit avec un grain de sel car je vais exposer mon point de vue personnel sur la façon d'adopter BLoC pour votre application.

BLoC est une approche très prometteuse pour la gestion d'état dans Flutter en raison d'un ingrédient de signature: les flux. Ils permettent de découpler l'interface utilisateur de la logique métier et ils fonctionnent bien avec l'approche Flutter-ish de reconstruction de sous-arborescences de widgets entiers une fois qu'ils sont obsolètes. Alors naturellement, chaque communication depuis et vers le BLoC devrait utiliser des flux, non?

try {
  setState(() => _isLoading = true); // This could display a loading spinner of sorts.
  await Bloc.of(context).signIn(_usernameController.text, _passwordController.text);
  Navigator.of(context).pushReplacement(...); // Push logged in screen.
} catch (e) {
  setState(() => _isLoading = false);
  // TODO: Display the error on the screen.
}

Bon type de.

La chose importante à retenir est que l'architecture de gestion d'état est un moyen pour atteindre une fin ; vous ne devriez pas simplement faire des choses pour le plaisir, mais gardez l'esprit ouvert et évaluez soigneusement les avantages et les inconvénients de chaque option. La raison pour laquelle nous séparons le BLoC de l'interface utilisateur est que le BLoC n'a pas besoin de se soucier de la façon dont l'interface utilisateur est structurée - il fournit juste de jolis flux simples et tout ce qui se passe avec les données est de la responsabilité de l'interface utilisateur.

Mais si les flux se sont avérés être un moyen fantastique de transporter des informations du BLoC vers l'interface utilisateur, ils ajoutent une surcharge inutile dans l'autre sens: les flux ont été conçus pour transporter des flux continus de données (c'est même dans le nom), mais la plupart des temps, l'interface utilisateur doit simplement déclencher des événements uniques dans le BLoC. C'est pourquoi parfois vous voyez des Stream<void> ou des solutions de piratage similaires¹, juste pour adhérer à la manière strictement BLoC-y de faire les choses.

De plus, si nous poussions de nouvelles routes basées sur le flux du BLoC, le BLoC contrôlerait essentiellement le flux de l'interface utilisateur - mais avoir un code qui contrôle directement à la fois l'interface utilisateur et la logique métier est exactement ce que nous avons essayé d'éviter!

C'est pourquoi certains développeurs (y compris moi) rompent avec la solution entièrement basée sur le flux et adoptent une manière personnalisée de déclencher des événements dans le BLoC à partir de l'interface utilisateur. Personnellement, j'utilise simplement des appels de méthode (qui renvoient généralement des Future s) pour déclencher les événements du BLoC:

+----+   method calls    +------+
| UI | ----------------> | BLoC |
|    | <---------------- |      |
+----+   Stream, Future  +------+

Ici, le BLoC renvoie Stream s pour les données "live" et Future s comme réponses aux appels de méthode.

Voyons comment cela pourrait fonctionner pour votre exemple:

  • Le BLoC peut fournir un Stream<bool> indiquant si l'utilisateur est connecté, ou même un Stream<Account> , où Account contient les informations de compte de l'utilisateur.
  • Le BLoC peut également fournir une méthode asynchrone Future<void> signIn(String username, String password) qui ne renvoie rien si la connexion a réussi ou renvoie une erreur dans le cas contraire.
  • L'interface utilisateur peut gérer elle-même la gestion des entrées et déclencher quelque chose comme ce qui suit une fois que le bouton de connexion est appuyé:
+----+  Stream   +------+
| UI | --------> | BLoC |
|    | <-------- |      |
+----+   Stream  +------+

De cette façon, vous obtenez une belle séparation des préoccupations:

  • Le BLoC fait vraiment ce qu'il est censé faire - gérer la logique métier (dans ce cas, connecter l'utilisateur).
  • L'interface utilisateur ne se soucie que de deux choses:
    • Affichage des données utilisateur des Stream s et
    • réagir aux actions de l'utilisateur en les déclenchant dans le BLoC et en exécutant des actions de l'interface utilisateur en fonction du résultat .²

Enfin, je tiens à souligner qu'il ne s'agit que d'une solution possible qui a évolué au fil du temps en essayant différentes façons de gérer l'état dans une application complexe. Il est important de connaître différents points de vue sur la façon dont la gestion des états pourrait fonctionner. Je vous encourage donc à approfondir ce sujet, peut-être en regardant la session "Gestion pragmatique de l'état dans Flutter" de Google I / O.

EDIT : Je viens de trouver cette architecture dans les échantillons d' architecture de Brian Egan , où elle s'appelle "Simple BLoC". Si vous voulez connaître différentes architectures, je vous recommande vraiment de jeter un œil au repo.


¹ Cela devient encore plus laid lorsque vous essayez de fournir plusieurs arguments à une action BLoC - parce qu'alors vous auriez besoin de définir une classe wrapper juste pour la transmettre au Stream.

² je l' admets devient un peu laid au démarrage de l'application: Vous aurez besoin d' une sorte d'écran de démarrage qui vérifie juste flux de l'bLoc et redirige l'utilisateur vers l'écran approprié en fonction qu'ils aient signé ou non. Cette exception à la règle se produit parce que l'utilisateur a effectué une action - démarrer l'application - mais le framework Flutter ne nous permet pas directement de s'y accrocher (du moins pas de manière élégante, pour autant que je sache).


0 commentaires

13
votes

Le BlocListener est le widget dont vous avez probablement besoin. Si l'état passe à (par exemple) LoginSuccess , l'auditeur de bloc peut alors appeler le Navigate.of(context) habituel. Vous pouvez trouver un exemple de BlocListener en action au bas de cette page .

Une autre option consiste à transmettre un rappel à l'événement.

 BlocProvider.of<MyBloc>(context).add(MyEvent(
              data: data,
              onSuccess: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) {
                    return HomePage();
                  }),
                );
              }));


2 commentaires

les liens sont maintenant partis


Merci @ C.Skjerdal Je l'ai corrigé maintenant.



2
votes

Tout d'abord: s'il n'y a pas de logique métier, il n'est pas nécessaire de passer à la classe YourBloc.

Mais de temps en temps, l'activité de certains utilisateurs est nécessaire pour exécuter une logique dans la classe Bloc , puis la classe Bloc doit décider quoi faire ensuite: il suffit de reconstruire les widgets ou d' afficher le dialogue ou même de naviguer vers l'itinéraire suivant . Dans ce cas, vous devez envoyer un état à l'interface utilisateur pour terminer l'action. Puis un autre problème apparaît: que dois-je faire avec les widgets lorsque le Bloc envoie State toast?

Et c'est le principal problème de toute cette histoire.

De nombreuses réponses et articles recommandent d'utiliser flutter_block . Cette bibliothèque a BlocBuilder et BlocListener . Avec ces cours, vous pouvez résoudre certains problèmes, mais pas à 100%.

Dans mon cas , je BlocConsumer qui gèrent BlocBuilder et BlocListener et de fournir excellente manière de gérer les états.

De la documentation:

BlocConsumer<BlocA, BlocAState>(
  listenWhen: (previous, current) {
    // return true/false to determine whether or not
    // to invoke listener with state
  },
  listener: (context, state) {
    // do stuff here based on BlocA's state
  },
  buildWhen: (previous, current) {
    // return true/false to determine whether or not
    // to rebuild the widget with state
  },
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)

Comme vous pouvez le voir avec BlocConsumer, vous pouvez filtrer les états: vous pouvez facilement définir des états pour reconstruire des widgets et des états pour afficher des fenêtres contextuelles ou accéder à l'écran suivant.


0 commentaires

0
votes

Comme mentionné par felangel dans Github dans le numéro suivant, nous pouvons utiliser BlocListner à cette fin.

BlocListener(
    bloc: BlocProvider.of<DataBloc>(context),
    listener: (BuildContext context, DataState state) {
        if (state is Success) {              
            Navigator.of(context).pushNamed('/details');
        }              
    },
    child: BlocBuilder(
        bloc: BlocProvider.of<DataBloc>(context),
        builder: (BuildContext context, DataState state) {        
            if (state is Initial) {
                return Text('Press the Button');
            }
            if (state is Loading) {
                return CircularProgressIndicator();
            }  
            if (state is Success) {
                return Text('Success');
            }  
            if (state is Failure) {
                return Text('Failure');
            }
        },
    }
)


0 commentaires