143 votes

Comment travailler avec l'indicateur de progression dans Flutter ?

Je suis novice dans Flutter et je voulais savoir quelle était la meilleure façon d'ajouter des données à la base de données. CircularProgressIndicator dans ma maquette. Par exemple, ma vue de connexion. Cette vue contient le nom d'utilisateur, le mot de passe et le bouton de connexion. J'ai voulu créer une mise en page superposée (avec la fonction Opacity ) qui, lors du chargement, affiche un indicateur de progression comme je l'utilise en NativeScript, mais je suis un peu confus sur la façon de faire et aussi si c'est la meilleure façon. En NativeScript, par exemple, j'ajoute IndicatorActivity dans le layout principal et je fixe busy à true ou false, de sorte qu'il recouvre tous les composants de la vue lors du chargement.

Edit :

J'ai pu arriver à ce résultat :

    void main() {
      runApp(new MyApp());
    }

    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: 'Flutter Demo',
          theme: new ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: new MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }

    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);

      final String title;

      @override
      _MyHomePageState createState() => new _MyHomePageState();
    }

    class _MyHomePageState extends State<MyHomePage> {
      bool _loading = false;

      void _onLoading() {
        setState(() {
          _loading = true;
          new Future.delayed(new Duration(seconds: 3), _login);
        });
      }

      Future _login() async{
        setState((){
          _loading = false;
        });
      }

      @override
      Widget build(BuildContext context) {

          var body = new Column(
              children: <Widget>[
                new Container(
                  height: 40.0,
                  padding: const EdgeInsets.all(10.0),
                  margin: const EdgeInsets.fromLTRB(15.0, 150.0, 15.0, 0.0),
                  decoration: new BoxDecoration(
                    color: Colors.white,
                  ),
                  child: new TextField(
                    decoration: new InputDecoration.collapsed(hintText: "username"),
                  ),
                ),
                new Container(
                  height: 40.0,
                  padding: const EdgeInsets.all(10.0),
                  margin: const EdgeInsets.all(15.0),
                  decoration: new BoxDecoration(
                    color: Colors.white,
                  ),
                  child: new TextField(
                    decoration: new InputDecoration.collapsed(hintText: "password"),
                  ),
                ),
              ],
            );

          var bodyProgress = new Container(
            child: new Stack(
              children: <Widget>[
                body,
                new Container(
                  alignment: AlignmentDirectional.center,
                  decoration: new BoxDecoration(
                    color: Colors.white70,
                  ),
                  child: new Container(
                    decoration: new BoxDecoration(
                      color: Colors.blue[200],
                      borderRadius: new BorderRadius.circular(10.0)
                    ),
                    width: 300.0,
                    height: 200.0,
                    alignment: AlignmentDirectional.center,
                    child: new Column(
                      crossAxisAlignment: CrossAxisAlignment.center,
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        new Center(
                          child: new SizedBox(
                            height: 50.0,
                            width: 50.0,
                            child: new CircularProgressIndicator(
                              value: null,
                              strokeWidth: 7.0,
                            ),
                          ),
                        ),
                        new Container(
                          margin: const EdgeInsets.only(top: 25.0),
                          child: new Center(
                            child: new Text(
                              "loading.. wait...",
                              style: new TextStyle(
                                color: Colors.white
                              ),
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          );

          return new Scaffold(
            appBar: new AppBar(
              title: new Text(widget.title),
            ),
            body: new Container(
              decoration: new BoxDecoration(
                color: Colors.blue[200]
              ),
              child: _loading ? bodyProgress : body
            ),
            floatingActionButton: new FloatingActionButton(
              onPressed: _onLoading,
              tooltip: 'Loading',
              child: new Icon(Icons.check),
            ),
          );
      }
    }

app screen result

Je suis encore en train de m'adapter à l'idée des états. Ce code est conforme à ce qui est attendu lorsque l'on travaille avec Flutter ?

Merci !

2 votes

Comment désactiver le retour en arrière pendant que le dialogue est affiché ?

133voto

Rémi Rousselet Points 45139

Dans Flutter, il y a plusieurs façons de traiter les actions asynchrones.

Une façon paresseuse de le faire peut être d'utiliser une modale. Celle-ci bloquera l'entrée de l'utilisateur, empêchant ainsi toute action indésirable. Cela nécessiterait très peu de changements dans votre code. Il suffit de modifier votre _onLoading à quelque chose comme ça :

void _onLoading() {
  showDialog(
    context: context,
    barrierDismissible: false,
    child: new Dialog(
      child: new Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          new CircularProgressIndicator(),
          new Text("Loading"),
        ],
      ),
    ),
  );
  new Future.delayed(new Duration(seconds: 3), () {
    Navigator.pop(context); //pop dialog
    _login();
  });
}

La façon la plus idéale de le faire est d'utiliser FutureBuilder et un widget à état. C'est ce que vous avez commencé. L'astuce est que, au lieu d'avoir un boolean loading = false dans votre État, vous pouvez directement utiliser un Future<MyUser> user

Et ensuite le passer comme argument à FutureBuilder qui vous donnera quelques informations telles que "hasData" ou l'instance de MyUser une fois terminé.

Cela conduirait à quelque chose comme ceci :

@immutable
class MyUser {
  final String name;

  MyUser(this.name);
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Future<MyUser> user;

  void _logIn() {
    setState(() {
      user = new Future.delayed(const Duration(seconds: 3), () {
        return new MyUser("Toto");
      });
    });
  }

  Widget _buildForm(AsyncSnapshot<MyUser> snapshot) {
    var floatBtn = new RaisedButton(
      onPressed:
          snapshot.connectionState == ConnectionState.none ? _logIn : null,
      child: new Icon(Icons.save),
    );
    var action =
        snapshot.connectionState != ConnectionState.none && !snapshot.hasData
            ? new Stack(
                alignment: FractionalOffset.center,
                children: <Widget>[
                  floatBtn,
                  new CircularProgressIndicator(
                    backgroundColor: Colors.red,
                  ),
                ],
              )
            : floatBtn;

    return new ListView(
      padding: const EdgeInsets.all(15.0),
        children: <Widget>[
          new ListTile(
            title: new TextField(),
          ),
          new ListTile(
            title: new TextField(obscureText: true),
          ),
          new Center(child: action)
        ],
    );
  }

  @override
  Widget build(BuildContext context) {
    return new FutureBuilder(
      future: user,
      builder: (context, AsyncSnapshot<MyUser> snapshot) {
        if (snapshot.hasData) {
          return new Scaffold(
            appBar: new AppBar(
              title: new Text("Hello ${snapshot.data.name}"),
            ),
          );
        } else {
          return new Scaffold(
            appBar: new AppBar(
              title: new Text("Connection"),
            ),
            body: _buildForm(snapshot),
          );
        }
      },
    );
  }
}

1 votes

Cool, les deux exemples seront utiles lors de la connexion et dans d'autres situations. La progression du Handler avec le dialogue semble meilleure que ma version et FutureBuilder est plus élégant que ma solution aussi. Merci pour votre aide !

0 votes

Une question hors sujet pour chaque TextField j'ai besoin d'un TextEditingController unique ?

0 votes

@RicardoBocchi Oui

57voto

aziza Points 11732

Pour moi, une façon élégante de faire cela est de présenter une SnackBar en bas de l'écran pendant la procédure d'inscription, voici un exemple de ce que je veux dire :

enter image description here

Voici comment configurer le SnackBar .

Définissez une clé globale pour votre Scaffold

final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();

Ajoutez-le à votre Scaffold key attribut

return new Scaffold(
      key: _scaffoldKey,
.......

Mon bouton de connexion onPressed le rappel :

onPressed: () {
                  _scaffoldKey.currentState.showSnackBar(
                      new SnackBar(duration: new Duration(seconds: 4), content:
                      new Row(
                        children: <Widget>[
                          new CircularProgressIndicator(),
                          new Text("  Signing-In...")
                        ],
                      ),
                      ));
                  _handleSignIn()
                      .whenComplete(() =>
                      Navigator.of(context).pushNamed("/Home")
                  );
                }

Cela dépend vraiment de la façon dont vous voulez construire votre réseau, et je ne suis pas sûr de ce que vous avez en tête.

Modifier

C'est probablement ce que vous voulez. J'ai utilisé une pile pour obtenir ce résultat et j'ai simplement affiché ou masqué mon indicateur en fonction de la valeur de l'indicateur. onPressed

enter image description here

class TestSignInView extends StatefulWidget {
  @override
  _TestSignInViewState createState() => new _TestSignInViewState();
}

class _TestSignInViewState extends State<TestSignInView> {
  bool _load = false;
  @override
  Widget build(BuildContext context) {
    Widget loadingIndicator =_load? new Container(
      color: Colors.grey[300],
      width: 70.0,
      height: 70.0,
      child: new Padding(padding: const EdgeInsets.all(5.0),child: new Center(child: new CircularProgressIndicator())),
    ):new Container();
    return new Scaffold(
      backgroundColor: Colors.white,
      body:  new Stack(children: <Widget>[new Padding(
        padding: const EdgeInsets.symmetric(vertical: 50.0, horizontal: 20.0),
        child: new ListView(

          children: <Widget>[
            new Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center
              ,children: <Widget>[
            new TextField(),
            new TextField(),

            new FlatButton(color:Colors.blue,child: new Text('Sign In'),
                onPressed: () {
              setState((){
                _load=true;
              });

                  //Navigator.of(context).push(new MaterialPageRoute(builder: (_)=>new HomeTest()));
                }
            ),

            ],),],
        ),),
        new Align(child: loadingIndicator,alignment: FractionalOffset.center,),

      ],));
  }

}

0 votes

Bonjour, c'est ce que je voulais faire, mais je n'obtenais pas la mise en page dont j'avais besoin. La pile est la solution. À propos de StatefulWidget, est-il correct de construire toutes les vues lorsque l'état de progression change ?

0 votes

Hé, je ne comprends pas votre question ?

0 votes

Dans mon code, lorsque _loading changement, toutes les vues sont reconstruites. Est-ce le cas ?

49voto

Harsha pulikollu Points 332

Créer un bool isLoading et le régler sur false . Avec l'aide de l'opérateur ternaire, lorsque l'utilisateur clique sur le bouton de connexion, l'état de isLoading à true . Vous obtiendrez un indicateur de chargement circulaire à la place du bouton de connexion.

 isLoading ? new PrimaryButton(
                      key: new Key('login'),
                      text: 'Login',
                      height: 44.0,
                      onPressed: setState((){isLoading = true;}))
                  : Center(
                      child: CircularProgressIndicator(),
                    ),

Vous pouvez voir des captures d'écran de ce à quoi cela ressemble avant que la connexion ne soit cliquée. enter image description here

Après avoir cliqué sur le bouton de connexion enter image description here

Pendant ce temps, vous pouvez exécuter le processus de connexion et connecter l'utilisateur. Si les informations d'identification de l'utilisateur sont erronées, alors vous devrez encore une fois setState de isLoading à false de sorte que l'indicateur de chargement devienne invisible et que le bouton de connexion soit visible pour l'utilisateur. A propos, le primaryButton utilisé dans le code est mon bouton personnalisé. Vous pouvez faire de même avec OnPressed sur button .

0 votes

C'est en fait assez intelligent ! Pas besoin de gérer le double clic, etc. Merci.

0 votes

Comment gérer le double-clic dans flutter comme ce scénario ?

0 votes

Je n'ai jamais réussi à gérer cette situation de double tap, car en cas de simple tap, l'indicateur de chargement apparaît. D'après ce que j'ai compris de votre commentaire, je pense que nous pouvons envelopper le bouton personnalisé avec le détecteur de gestes et ensuite vous pouvez travailler sur le double tap.

31voto

Shyju Madathil Points 582

1. Sans plugin

    class IndiSampleState extends State<ProgHudPage> {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
          title: new Text('Demo'),
        ),
        body: Center(
          child: RaisedButton(
            color: Colors.blueAccent,
            child: Text('Login'),
            onPressed: () async {
              showDialog(
                  context: context,
                  builder: (BuildContext context) {
                    return Center(child: CircularProgressIndicator(),);
                  });
              await loginAction();
              Navigator.pop(context);
            },
          ),
        ));
  }

  Future<bool> loginAction() async {
    //replace the below line of code with your login request
    await new Future.delayed(const Duration(seconds: 2));
    return true;
  }
}

2. Avec plugin

Vérifiez ce plugin progress_hud

ajouter la dépendance dans le fichier pubspec.yaml

dev_dependencies:
  progress_hud: 

Importer le paquet

import 'package:progress_hud/progress_hud.dart';

Un exemple de code est donné ci-dessous pour afficher et masquer l'indicateur.

class ProgHudPage extends StatefulWidget {
  @override
  _ProgHudPageState createState() => _ProgHudPageState();
}

class _ProgHudPageState extends State<ProgHudPage> {
  ProgressHUD _progressHUD;
  @override
  void initState() {
    _progressHUD = new ProgressHUD(
      backgroundColor: Colors.black12,
      color: Colors.white,
      containerColor: Colors.blue,
      borderRadius: 5.0,
      loading: false,
      text: 'Loading...',
    );
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
          title: new Text('ProgressHUD Demo'),
        ),
        body: new Stack(
          children: <Widget>[
            _progressHUD,
            new Positioned(
                child: RaisedButton(
                  color: Colors.blueAccent,
                  child: Text('Login'),
                  onPressed: () async{
                    _progressHUD.state.show();
                    await loginAction();
                    _progressHUD.state.dismiss();
                  },
                ),
                bottom: 30.0,
                right: 10.0)
          ],
        ));
  }

  Future<bool> loginAction()async{
    //replace the below line of code with your login request
    await new Future.delayed(const Duration(seconds: 2));
    return true;
  }
}

13 votes

Ne votez pas contre, certaines personnes ne veulent pas s'occuper des détails de l'interface utilisateur. Je suis l'une d'entre elles et ce plugin est donc très utile.

4 votes

La barre de progression dans l'api est assez juste l'ajout de dépendances augmente la taille de la construction. la construction de flutter est déjà excessive.

0 votes

Devriez-vous vraiment l'ajouter en tant que dépendance de développement ?

13voto

mmccabe Points 1200

J'ai adopté l'approche suivante, qui utilise un simple widget d'indicateur de progression modal qui enveloppe tout ce que vous voulez rendre modal pendant un appel asynchrone.

L'exemple du paquetage aborde également la manière de gérer la validation du formulaire tout en effectuant des appels asynchrones pour valider le formulaire (cf. flutter/issues/9688 pour les détails de ce problème). Par exemple, sans quitter le formulaire, cette méthode de validation asynchrone du formulaire peut être utilisée pour valider un nouveau nom d'utilisateur par rapport aux noms existants dans une base de données lors de l'inscription.

https://pub.dartlang.org/packages/modal_progress_hud

Voici la démonstration de l'exemple fourni avec le paquet (avec le code source) :

async form validation with modal progress indicator

L'exemple pourrait être adapté à d'autres comportements d'indicateurs de progression de la modale (comme des animations différentes, du texte supplémentaire dans la modale, etc.)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X