245 votes

Forcer le navigateur de Flutter à recharger l'état lorsqu'il s'ouvre.

J'en ai un. StatefulWidget dans Flutter avec le bouton, qui me dirige vers une autre StatefulWidget en utilisant Navigator.push() . Sur le deuxième widget, je modifie l'état global (certaines préférences de l'utilisateur). Lorsque je reviens du deuxième widget au premier, en utilisant la commande Navigator.pop() le premier widget est dans un ancien état, mais je veux forcer son rechargement. Savez-vous comment faire ? J'ai une idée mais c'est moche :

  1. pop pour enlever le deuxième widget (celui en cours)
  2. pop again pour enlever le premier widget (le précédent)
  3. pousser le premier widget (cela devrait forcer le redessin)

1 votes

Pas de réponse, juste un commentaire général : dans mon cas, ce qui m'a amené à chercher ici serait résolu avec une simple méthode de synchronisation pour shared_preferences où je suis assuré de récupérer une préfixe mise à jour que j'ai écrite il y a un instant dans une autre page :-) Même l'utilisation de .then(...) ne me permet pas toujours d'obtenir les données mises à jour en attente d'écriture.

1 votes

Je viens de renvoyer une valeur de la nouvelle page sur pop, ce qui a résolu mon problème. Voir flutter.dev/docs/cookbook/navigation/returning-data

0 votes

157voto

rmtmckenzie Points 10854

Il y a plusieurs choses que vous pouvez faire ici. La réponse de @Mahi, bien que correcte, pourrait être un peu plus succincte et utiliser push plutôt que showDialog comme le demandait le PO. Voici un exemple qui utilise Navigator.push :

import 'package:flutter/material.dart';

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.green,
      child: Column(
        children: <Widget>[
          RaisedButton(
            onPressed: () => Navigator.pop(context),
            child: Text('back'),
          ),
        ],
      ),
    );
  }
}

class FirstPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => new FirstPageState();
}

class FirstPageState extends State<FirstPage> {

  Color color = Colors.white;

  @override
  Widget build(BuildContext context) {
    return new Container(
      color: color,
      child: Column(
        children: <Widget>[
          RaisedButton(
            child: Text("next"),
            onPressed: () async {
              final value = await Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => SecondPage()),
                ),
              );
              setState(() {
                color = color == Colors.white ? Colors.grey : Colors.white;
              });
            },
          ),
        ],
      ),
    );
  }
}

void main() => runApp(
      MaterialApp(
        builder: (context, child) => SafeArea(child: child),
        home: FirstPage(),
      ),
    );

Cependant, il existe une autre façon de procéder qui pourrait bien correspondre à votre cas d'utilisation. Si vous utilisez le global comme quelque chose qui affecte la construction de votre première page, vous pourriez utiliser une InheritedWidget pour définir vos préférences globales d'utilisateur, et chaque fois qu'elles seront modifiées, votre FirstPage sera reconstruite. Cela fonctionne même dans un widget sans état, comme illustré ci-dessous (mais devrait également fonctionner dans un widget avec état).

Un exemple de inheritedWidget dans flutter est le thème de l'application, bien qu'ils le définissent dans un widget au lieu de le construire directement comme je l'ai fait ici.

import 'package:flutter/material.dart';
import 'package:meta/meta.dart';

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.green,
      child: Column(
        children: <Widget>[
          RaisedButton(
            onPressed: () {
              ColorDefinition.of(context).toggleColor();
              Navigator.pop(context);
            },
            child: new Text("back"),
          ),
        ],
      ),
    );
  }
}

class ColorDefinition extends InheritedWidget {
  ColorDefinition({
    Key key,
    @required Widget child,
  }): super(key: key, child: child);

  Color color = Colors.white;

  static ColorDefinition of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(ColorDefinition);
  }

  void toggleColor() {
    color = color == Colors.white ? Colors.grey : Colors.white;
    print("color set to $color");
  }

  @override
  bool updateShouldNotify(ColorDefinition oldWidget) =>
      color != oldWidget.color;
}

class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var color = ColorDefinition.of(context).color;

    return new Container(
      color: color,
      child: new Column(
        children: <Widget>[
          new RaisedButton(
              child: new Text("next"),
              onPressed: () {
                Navigator.push(
                  context,
                  new MaterialPageRoute(builder: (context) => new SecondPage()),
                );
              }),
        ],
      ),
    );
  }
}

void main() => runApp(
      new MaterialApp(
        builder: (context, child) => new SafeArea(
              child: new ColorDefinition(child: child),
            ),
        home: new FirstPage(),
      ),
    );

Si vous utilisez le widget hérité, vous n'avez pas à vous soucier de surveiller le pop de la page que vous avez poussé, ce qui fonctionnera pour les cas d'utilisation de base mais peut finir par poser des problèmes dans un scénario plus complexe.

0 votes

Excellent, le premier cas a parfaitement fonctionné pour moi (le second est parfait pour une situation plus complexe). L'utilisation de OnWillPopUp à partir de la deuxième page n'était pas claire pour moi ; et ne fonctionnait même pas du tout.

0 votes

Hé, et si je veux mettre à jour mon élément de liste. Disons que ma première page contient des éléments de liste et que la deuxième page contient le détail de l'élément. Je mets à jour la valeur de l'élément et je veux qu'elle soit remise à jour dans la liste des éléments. Comment faire ?

0 votes

@AanalMehta Je peux vous donner une recommandation rapide - soit vous avez un backend store (i.e. table sqflite ou liste en mémoire) qui est utilisé à partir des deux pages, et dans le .then vous pourriez alors forcer votre widget à se rafraîchir d'une manière ou d'une autre (j'ai personnellement utilisé un compteur incrémental que je change dans setState avant - ce n'est pas la solution la plus propre mais ça marche)... Ou bien, vous pourriez passer les changements à la page originale dans la fonction .then, faire les modifications à votre liste, et ensuite reconstruire (mais notez que faire des changements à une liste ne déclenche pas un rafraîchissement, donc encore une fois utilisez un compteur incrémentiel).

95voto

CopsOnRoad Points 4705

Réponse courte :

Utilisez ceci en 1ère page :

Navigator.pushNamed(context, '/page2').then((_) => setState(() {}));

et ceci en 2ème page :

Navigator.pop(context);

Il y a 2 choses, passer des données de

  • 1ère page vers 2ème page

    Utilisez ceci en 1ère page

      // sending "Foo" from 1st
      Navigator.push(context, MaterialPageRoute(builder: (_) => Page2("Foo")));

    Utilisez ceci en 2ème page.

      class Page2 extends StatelessWidget {
        final String string;
    
        Page2(this.string); // receiving "Foo" in 2nd
    
        ...
      }

  • 2ème page vers 1ère page

    Utilisez ceci en 2ème page

      // sending "Bar" from 2nd
      Navigator.pop(context, "Bar");

    Utilisez ceci en 1ère page, c'est le même que celui utilisé précédemment mais avec une petite modification.

      // receiving "Bar" in 1st
      String received = await Navigator.push(context, MaterialPageRoute(builder: (_) => Page2("Foo")));

1 votes

Comment puis-je recharger la route A lorsque je fais un saut depuis la route C en utilisant la méthode Navigator.popUntil.

1 votes

@VinothVino Il n'y a pas encore de moyen direct de faire cela, vous devez faire une sorte de contournement.

0 votes

@CopsOnRoad dans l'activité TabBar pour appeler le deuxième onglet à partir de la boîte de dialogue clic sur le bouton submit du Navigateur.pushreplacement pour rediriger vers le deuxième onglet.

12voto

Sobhan Jachuck Points 21

Vous pouvez utiliser pushReplacement et spécifier la nouvelle Route

15 votes

Mais vous perdez la pile de navigation. Et si vous faites sauter l'écran en utilisant le bouton retour d'Android ?

0 votes

Même pushreplacement ne reconstruira pas le contexte et l'état s'il a été chargé avant, je suppose.

6voto

Mathew Varghese Points 21

Mettez ceci à l'endroit où vous allez pousser vers le second écran (à l'intérieur d'une fonction asynchrone).

Function f;
f= await Navigator.pushNamed(context, 'ScreenName');
f();

Mets ça là où tu es en train d'éclater

Navigator.pop(context, () {
 setState(() {});
});

Le site setState est appelé à l'intérieur du pop pour mettre à jour les données.

2 votes

Où exactement passez-vous setState comme argument ? En gros, vous appelez setState à l'intérieur d'une fermeture. Ne pas le passer comme argument.

1 votes

C'est exactement la réponse que je cherchais. Je voulais essentiellement un gestionnaire d'achèvement pour Navigator.pop .

4voto

Mahi Points 1132

Vous pouvez renvoyer un dynamic result lorsque vous faites apparaître le contexte, puis appelez la fonction setState((){}) lorsque la valeur est true sinon, il suffit de laisser l'État tel qu'il est.

J'ai collé quelques extraits de code pour votre référence.

handleClear() async {
    try {
      var delete = await deleteLoanWarning(
        context,
        'Clear Notifications?',
        'Are you sure you want to clear notifications. This action cannot be undone',
      );
      if (delete.toString() == 'true') {
        //call setState here to rebuild your state.

      }
    } catch (error) {
      print('error clearing notifications' + error.toString());
             }
  }

Future<bool> deleteLoanWarning(BuildContext context, String title, String msg) async {

  return await showDialog<bool>(
        context: context,
        child: new AlertDialog(
          title: new Text(
            title,
            style: new TextStyle(fontWeight: fontWeight, color: CustomColors.continueButton),
            textAlign: TextAlign.center,
          ),
          content: new Text(
            msg,
            textAlign: TextAlign.justify,
          ),
          actions: <Widget>[
            new Container(
              decoration: boxDecoration(),
              child: new MaterialButton(
                child: new Text('NO',),
                onPressed: () {
                  Navigator.of(context).pop(false);
                },
              ),
            ),
            new Container(
              decoration: boxDecoration(),
              child: new MaterialButton(
                child: new Text('YES', ),
                onPressed: () {
                  Navigator.of(context).pop(true);
                },
              ),
            ),
          ],
        ),
      ) ??
      false;
}

Regards, Mahi

0 votes

Je ne suis pas sûr de comprendre comment faire la deuxième partie. La première est simplement Navigator.pop(context, true) n'est-ce pas ? Mais comment puis-je obtenir ceci true valeur ? Utilisation de BuildContext ?

0 votes

Ça ne marche pas pour moi. J'ai utilisé le résultat de push, appelé setState et obtenu setState() called after dispose(): This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback. The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. ....

0 votes

Hmm, c'est intéressant, avez-vous utilisé if(!mounted) return; avant chaque appel à setState((){}); De cette façon, vous pouvez éviter de mettre à jour les widgets éliminés ou les widgets qui ne sont plus actifs.

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