Erzwingen Sie, dass der Flutter-Navigator den Status beim Poppen neu lädt


97

Ich habe eine StatefulWidgetin Flutter mit Knopf, die mich zu einem anderen navigiert StatefulWidgetmit Navigator.push(). Beim zweiten Widget ändere ich den globalen Status (einige Benutzereinstellungen). Wenn ich vom zweiten zum ersten Widget zurückkehre, befindet sich die Verwendung Navigator.pop()des ersten Widgets im alten Zustand, aber ich möchte das Neuladen erzwingen. Irgendeine Idee, wie das geht? Ich habe eine Idee, aber sie sieht hässlich aus:

  1. Pop, um das zweite Widget zu entfernen (aktuelles)
  2. Pop erneut, um das erste Widget zu entfernen (vorheriges)
  3. Erstes Widget drücken (es sollte das Neuzeichnen erzwingen)

1
Keine Antwort, nur ein allgemeiner Kommentar: In meinem Fall würde das, was mich hierher gebracht hat, um dies zu suchen, mit einer Synchronisierungsmethode für shared_preferences gelöst, bei der ich garantiert eine aktualisierte Voreinstellung zurückerhalte, die ich vor kurzem auf einer anderen Seite geschrieben habe . : \ Selbst wenn ich .then (...) verwende, bekomme ich nicht immer die aktualisierten Daten zum ausstehenden Schreiben.
ChrisH

Ich habe gerade einen Wert von der neuen Seite auf Pop zurückgegeben und mein Problem behoben. Siehe flutter.dev/docs/cookbook/navigation/returning-data
Joe M

Antworten:


77

Es gibt ein paar Dinge, die Sie hier tun könnten. @ Mahis Antwort, obwohl sie korrekt ist, könnte etwas prägnanter sein und tatsächlich Push anstelle von showDialog verwenden, nach dem das OP gefragt hat. Dies ist ein Beispiel, das Folgendes verwendet Navigator.push:

import 'package:flutter/material.dart';

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Container(
      color: Colors.green,
      child: new Column(
        children: <Widget>[
          new RaisedButton(
            onPressed: () => Navigator.pop(context),
            child: new 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: new Column(
        children: <Widget>[
          new RaisedButton(
              child: new Text("next"),
              onPressed: () {
                Navigator
                    .push(
                  context,
                  new MaterialPageRoute(builder: (context) => new SecondPage()),
                )
                    .then((value) {
                  setState(() {
                    color = color == Colors.white ? Colors.grey : Colors.white;
                  });
                });
              }),
        ],
      ),
    );
  }
}

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

Es gibt jedoch eine andere Möglichkeit, die zu Ihrem Anwendungsfall passt. Wenn Sie das globalals etwas verwenden, das sich auf die Erstellung Ihrer ersten Seite auswirkt, können Sie ein InheritedWidget verwenden , um Ihre globalen Benutzereinstellungen zu definieren. Bei jeder Änderung wird Ihre FirstPage neu erstellt. Dies funktioniert sogar in einem zustandslosen Widget, wie unten gezeigt (sollte aber auch in einem zustandsbehafteten Widget funktionieren).

Ein Beispiel für vererbtes Widget im Flattern ist das Design der App, obwohl es innerhalb eines Widgets definiert wird, anstatt es wie hier direkt erstellen zu lassen.

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

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Container(
      color: Colors.green,
      child: new Column(
        children: <Widget>[
          new 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(),
      ),
    );

Wenn Sie ein geerbtes Widget verwenden, müssen Sie sich keine Gedanken darüber machen, ob die von Ihnen gepusste Seite angezeigt wird. Dies funktioniert für grundlegende Anwendungsfälle, kann jedoch in einem komplexeren Szenario zu Problemen führen.


Ausgezeichnet, der erste Fall hat bei mir perfekt funktioniert (der zweite ist perfekt für eine komplexere Situation). Die Verwendung von OnWillPopUp auf der zweiten Seite war mir nicht klar. und hat überhaupt nicht gearbeitet.
CDSAenz

Hey, was ist, wenn ich mein Listenelement aktualisieren möchte? Angenommen, meine erste Seite enthält Listenelemente und die zweite Seite enthält Artikeldetails. Ich aktualisiere den Wert des Elements und möchte, dass er bei Listenelementen wieder aktualisiert wird. Wie erreicht man das?
Aanal Mehta

@AanalMehta Ich kann Ihnen eine kurze Empfehlung geben - entweder einen Backend-Speicher (z. B. eine SQLite-Tabelle oder eine In-Memory-Liste), der von beiden Seiten verwendet wird, und dann können Sie Ihr Widget zwingen, es irgendwie zu aktualisieren (ich persönlich) habe einen inkrementellen Zähler verwendet, den ich zuvor in setState geändert habe - es ist nicht die sauberste Lösung, aber es funktioniert) ... Oder Sie können die Änderungen in der Funktion .then an die ursprüngliche Seite zurückgeben, die Änderungen an Ihrer Liste vornehmen und dann neu erstellen (beachten Sie jedoch, dass das Vornehmen von Änderungen an einer Liste keine Aktualisierung auslöst. Verwenden Sie daher erneut den inkrementierenden Zähler).
rmtmckenzie

@AanalMehta Aber wenn das nicht hilft, würde ich empfehlen, dass Sie nach anderen Antworten suchen, die möglicherweise relevanter sind (ich bin mir ziemlich sicher, dass ich einige über Listen usw. gesehen habe) oder eine neue Frage stellen.
rmtmckenzie

@rmtmckenzie Eigentlich habe ich die obige Lösung ausprobiert. Ich habe die Änderungen in .then angewendet, aber sie werden nicht wiedergegeben. Ich denke, jetzt habe ich nur eine Option, um Anbieter zu verwenden. Wie auch immer, vielen Dank für Ihre Hilfe.
Aanal Mehta

19

Es gibt zwei Dinge, von denen Daten übergeben werden

  • 1. Seite bis 2 ..

    Verwenden Sie dies auf der 1. Seite

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

    Verwenden Sie dies auf der 2. Seite.

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

  • 2. Seite bis 1 ..

    Verwenden Sie dies auf der 2. Seite

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

    Verwenden Sie dies auf der ersten Seite, es ist das gleiche, das früher verwendet wurde, aber mit wenig Modifikation.

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

Wie kann ich Route A neu laden, wenn ich mit der Navigator.popUntil-Methode von Route C abfalle?
Vinoth Vino

1
@VinothVino Es gibt noch keinen direkten Weg, dies zu tun. Sie müssen eine Art Problemumgehung durchführen.
CopsOnRoad

10

Sie können pushReplacement verwenden und die neue Route angeben


Sie verlieren jedoch den Navigatorstapel. Was ist, wenn Sie den Bildschirm mit der Android-Zurück-Taste öffnen?
encubos

10

Der einfache Trick besteht darin, die Navigator.pushReplacement- Methode zu verwenden

Seite 1

Navigator.pushReplacement(
  context,
  MaterialPageRoute(
    builder: (context) => Page2(),
  ),
);

Seite 2

Navigator.pushReplacement(
  context,
  MaterialPageRoute(
    builder: (context) => Page1(),
  ),
);

Auf diese Weise verlieren Sie den Navigatorstapel. Was ist mit dem Öffnen des Bildschirms mit der Zurück-Taste?
encubos

5

Diese Arbeit ist wirklich gut, ich habe von diesem Dokument von der Flatter-Seite bekommen: Flatter-Dokument

Ich habe die Methode zur Steuerung der Navigation von der ersten Seite an definiert.

_navigateAndDisplaySelection(BuildContext context) async {
    final result = await Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => AddDirectionPage()),
    );

    //below you can get your result and update the view with setState
    //changing the value if you want, i just wanted know if i have to  
    //update, and if is true, reload state

    if (result) {
      setState(() {});
    }
  }

Also nenne ich es in einer Aktionsmethode aus einem Tintenfass, kann aber auch über eine Schaltfläche aufgerufen werden:

onTap: () {
   _navigateAndDisplaySelection(context);
},

Und schließlich auf der zweiten Seite, um etwas zurückzugeben (ich habe einen Bool zurückgegeben, Sie können zurückgeben, was Sie wollen):

onTap: () {
  Navigator.pop(context, true);
}

1
Sie können sogar await Navigator....einen Ergebniswert in Pop einfach und weglassen.
25.

4

Stellen Sie dies dort ein, wo Sie zum zweiten Bildschirm wechseln (innerhalb einer asynchronen Funktion).

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

Platzieren Sie dies dort, wo Sie auftauchen

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

Das setStatewird innerhalb des popVerschlusses aufgerufen , um die Daten zu aktualisieren.


2
Wo genau gehen Sie setStateals Argument vor? Sie rufen setStateim Grunde genommen in einem Verschluss an. Nicht als Argument übergeben.
Volkan Güven

Dies ist die genaue Antwort, nach der ich gesucht habe. Ich wollte im Grunde einen Completion Handler für Navigator.pop.
David Chopin

2

Meine Lösung bestand darin, einen Funktionsparameter auf SecondPage hinzuzufügen, dann die Neuladefunktion zu erhalten, die von FirstPage ausgeführt wird, und dann die Funktion vor der Zeile Navigator.pop (Kontext) auszuführen.

Erste Seite

refresh() {
setState(() {
//all the reload processes
});
}

dann auf die nächste Seite schieben ...

Navigator.push(context, new MaterialPageRoute(builder: (context) => new SecondPage(refresh)),);

Zweite Seite

final Function refresh;
SecondPage(this.refresh); //constructor

dann weiter vor der Navigator-Popline,

widget.refresh(); // just refresh() if its statelesswidget
Navigator.pop(context);

Alles, was von der vorherigen Seite neu geladen werden muss, sollte nach dem Pop aktualisiert werden.


1

Sie können a zurückgeben, dynamic resultwenn Sie den Kontext öffnen, und dann aufrufen, setState((){})wenn der Wert trueandernfalls den Status unverändert lässt.

Ich habe einige Codefragmente als Referenz eingefügt.

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

Grüße, Mahi


Ich bin mir nicht sicher, wie ich den zweiten Teil machen soll. Erstens ist es einfach Navigator.pop(context, true), oder? Aber wie kann ich diesen trueWert erhalten? Verwenden BuildContext?
Bartektartanus

Ich arbeite nicht für mich. Ich habe das Ergebnis von Push verwendet, setState genannt und habesetState() 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. ....
bartektartanus

hmm, das ist interessant, haben Sie if(!mounted) return;vor jedem Aufruf auf setState((){});diese Weise verwendet, um zu vermeiden, dass die bereitgestellten Widgets oder Widgets, die nicht mehr aktiv sind, aktualisiert werden.
Mahi

Kein Fehler, aber es funktioniert auch nicht wie vorhergesagt;)
Bartektartanus

Ich habe das gleiche Problem wie @bartektartanus. Wenn ich if! Vor setState einbinde, wird mein Status niemals festgelegt. Es scheint einfach, aber ich habe es nach ein paar Stunden nicht herausgefunden.
Swift

1

Musste den Wiederaufbau eines meiner zustandslosen Widgets erzwingen. Wollte nicht stateful verwenden. Kam mit dieser Lösung:

await Navigator.of(context).pushNamed(...);
ModalRoute.of(enclosingWidgetContext);

Beachten Sie, dass Kontext und einschließender WidgetContext dieselben oder unterschiedliche Kontexte sein können. Wenn Sie beispielsweise aus StreamBuilder heraus pushen, sind diese unterschiedlich.

Wir machen hier nichts mit ModalRoute. Das Abonnieren allein reicht aus, um den Wiederaufbau zu erzwingen.


1

Wenn Sie ein Warnungsdialogfeld verwenden, können Sie eine Zukunft verwenden, die abgeschlossen wird, wenn das Dialogfeld geschlossen wird. Nach Abschluss der Zukunft können Sie das Widget zwingen, den Status neu zu laden.

Erste Seite

onPressed: () async {
    await showDialog(
       context: context,
       builder: (BuildContext context) {
            return AlertDialog(
                 ....
            );
       }
    );
    setState(() {});
}

Im Alarmdialog

Navigator.of(context).pop();

0

Heute war ich mit der gleichen Situation konfrontiert, aber es gelang mir, sie viel einfacher zu lösen. Ich habe nur eine globale Variable definiert, die in der ersten Stateful-Klasse verwendet wurde, und wenn ich zu einem zweiten Stateful-Widget navigiere, lasse ich sie den Wert der Global-Version aktualisieren Variable, die automatisch die Aktualisierung des ersten Widgets erzwingt. Hier ist ein Beispiel (ich habe es in Eile geschrieben, also habe ich kein Gerüst oder keine Material-App aufgestellt, ich wollte nur meinen Standpunkt veranschaulichen):

import 'package:flutter/material.dart';
int count = 0 ;

class FirstPage extends StatefulWidget {
FirstPage({Key key}) : super(key: key);

@override
_FirstPageState createState() => _FirstPageState();
}

class _FirstPageState extends State<FirstPage> {
@override
Widget build(BuildContext context) {
return InkWell(
onTap(){
Navigator.of(context).push(MaterialPageRoute(builder: (context) =>
                  new SecondPage());
},
child: Text('First', style : TextStyle(fontSize: count == 0 ? 20.0 : 12.0)),
),

}


class SecondPage extends StatefulWidget {
SecondPage({Key key}) : super(key: key);

@override
_SecondPageState createState() => _SecondPageState();
}

class _SecondPageState extends State<SecondPage> {
@override
Widget build(BuildContext context) {
return IconButton(
         icon: new Icon(Icons.add),
         color: Colors.amber,
         iconSize: 15.0,
         onPressed: (){
         count++ ;
         },
       ),
     }

4
Durch Ändern der Variablen wird das Widget nicht automatisch neu erstellt. NachNavigator.pop() es den Status vorher beibehalten hat, Navigator.push()bis setStatees erneut aufgerufen wird, was in diesem Code nicht passiert. Vermisse ich etwas
Ricardo BRGWeb

0

Dieser einfache Code funktionierte für mich, um zum Stammverzeichnis zu gelangen und den Status neu zu laden:

    ...
    onPressed: () {
         Navigator.of(context).pushNamedAndRemoveUntil('/', ModalRoute.withName('/'));
                },
    ...
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.