Mam jeden StatefulWidget
w Flutter z przyciskiem, który przenosi mnie do drugiego StatefulWidget
za pomocą Navigator.push()
. Na drugim widżecie zmieniam stan globalny (niektóre preferencje użytkownika). Kiedy wracam z drugiego widżetu do pierwszego, używanie Navigator.pop()
pierwszego widżetu jest w starym stanie, ale chcę wymusić jego ponowne załadowanie. Masz jakiś pomysł, jak to zrobić? Mam jeden pomysł, ale wygląda brzydko:
- pop, aby usunąć drugi widżet (bieżący)
- wyskocz ponownie, aby usunąć pierwszy widżet (poprzedni)
- push pierwszy widget (powinien wymusić przerysowanie)
Odpowiedzi:
Możesz tutaj zrobić kilka rzeczy. Odpowiedź @ Mahi, chociaż poprawna, mogłaby być nieco bardziej zwięzła i faktycznie używać wypychania zamiast showDialog, o co pytał OP. Oto przykład wykorzystujący
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(), ), );
Jest jednak inny sposób na zrobienie tego, który może dobrze pasować do twojego przypadku użycia. Jeśli używasz
global
jako czegoś, co wpływa na kompilację Twojej pierwszej strony, możesz użyć InheritedWidget, aby zdefiniować swoje globalne preferencje użytkownika, a za każdym razem, gdy zostaną one zmienione, Twoja FirstPage zostanie odbudowana. Działa to nawet w widgecie bezstanowym, jak pokazano poniżej (ale powinno również działać w widgecie stanowym).Przykładem inheritedWidget in flutter jest motyw aplikacji, chociaż definiują go w widżecie, zamiast budować go bezpośrednio, tak jak tutaj.
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(), ), );
Jeśli używasz dziedziczonego widżetu, nie musisz się martwić o wypatrywanie popchniętej strony, która będzie działać w podstawowych przypadkach użycia, ale może skończyć się problemami w bardziej złożonym scenariuszu.
źródło
Są dwie rzeczy, z których przekazywane są dane
Pierwsza strona do drugiej
Użyj tego na pierwszej stronie
// sending "Foo" from 1st Navigator.push(context, MaterialPageRoute(builder: (_) => Page2("Foo")));
Użyj tego na drugiej stronie.
class Page2 extends StatelessWidget { final String string; Page2(this.string); // receiving "Foo" in 2nd ... }
2. strona do 1. strony
Użyj tego na drugiej stronie
// sending "Bar" from 2nd Navigator.pop(context, "Bar");
Użyj tego na pierwszej stronie, jest to ten sam, który był używany wcześniej, ale z niewielkimi modyfikacjami.
// receiving "Bar" in 1st String received = await Navigator.push(context, MaterialPageRoute(builder: (_) => Page2("Foo")));
źródło
Łatwa Sztuczka polega na użyciu Navigator.pushReplacement metody
Strona 1
Strona 2
źródło
Możesz użyć pushReplacement i określić nową trasę
źródło
Ta praca jest naprawdę dobra, dostałem z tego dokumentu z flutter page: flutter doc
Zdefiniowałem metodę sterowania nawigacją od pierwszej strony.
_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(() {}); } }
Tak więc nazywam to metodą akcji z kałamarza, ale można ją również wywołać z przycisku:
I wreszcie na drugiej stronie, aby coś zwrócić (zwróciłem wartość bool, możesz zwrócić, co chcesz):
onTap: () { Navigator.pop(context, true); }
źródło
await Navigator....
pominąć wartość wynikową w pop.Wydaje mi się, że to działa:
Navigator.of(context).pushNamed("/myRoute").then((value) => setState(() {}));
Następnie po prostu wezwij
Navigator.pop()
dziecko.źródło
Umieść to tam, gdzie pchasz do drugiego ekranu (wewnątrz funkcji asynchronicznej)
Function f; f= await Navigator.pushNamed(context, 'ScreenName'); f();
Umieść to tam, gdzie strzelasz
setState
Nazywa się wewnątrzpop
zamknięcia, aby zaktualizować dane.źródło
setState
argument? W zasadzie dzwoniszsetState
wewnątrz zamknięcia. Nie podając tego jako argumentu.Navigator.pop
.Moje rozwiązanie polegało na dodaniu parametru funkcji na SecondPage, a następnie odebraniu funkcji przeładowania, która jest wykonywana z FirstPage, a następnie wykonaniu funkcji przed wierszem Navigator.pop (kontekst).
Pierwsza strona
refresh() { setState(() { //all the reload processes }); }
następnie przechodząc do następnej strony ...
Navigator.push(context, new MaterialPageRoute(builder: (context) => new SecondPage(refresh)),);
SecondPage
final Function refresh; SecondPage(this.refresh); //constructor
następnie przed wierszem pop nawigatora,
widget.refresh(); // just refresh() if its statelesswidget Navigator.pop(context);
Wszystko, co trzeba przeładować z poprzedniej strony, powinno zostać zaktualizowane po wyskakującym okienku.
źródło
Możesz przekazać z powrotem a,
dynamic result
gdy otwierasz kontekst, a następnie wywołać,setState((){})
gdy wartość jest wtrue
przeciwnym razie, po prostu pozostaw stan taki, jaki jest.Wkleiłem kilka fragmentów kodu w celach informacyjnych.
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; }
Pozdrawiam, Mahi
źródło
Navigator.pop(context, true)
, prawda? Ale jak mogę uzyskać tętrue
wartość? UżywaszBuildContext
?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. ....
if(!mounted) return;
przed każdym wywołaniem, wsetState((){});
ten sposób możesz uniknąć aktualizacji usuniętych widżetów lub widżetów, które nie są już aktywne.Potrzebne do wymuszenia przebudowy jednego z moich bezstanowych widżetów. Nie chciałem używać stanowych. Przyszło z tym rozwiązaniem:
await Navigator.of(context).pushNamed(...); ModalRoute.of(enclosingWidgetContext);
Zauważ, że kontekst i otaczający go WidgetContext mogą być tym samym lub różnymi kontekstami. Jeśli na przykład wypchniesz z wnętrza StreamBuilder, będą one inne.
Nic tu nie robimy z ModalRoute. Sama subskrypcja wystarczy, aby wymusić odbudowę.
źródło
Jeśli używasz okna dialogowego z ostrzeżeniem, możesz użyć opcji Przyszłość, która kończy się po zamknięciu okna dialogowego. Po zakończeniu przyszłości możesz zmusić widget do ponownego załadowania stanu.
Pierwsza strona
onPressed: () async { await showDialog( context: context, builder: (BuildContext context) { return AlertDialog( .... ); } ); setState(() {}); }
W oknie dialogowym Alert
źródło
Dzisiaj miałem taką samą sytuację, ale udało mi się ją rozwiązać w dużo łatwiejszy sposób, właśnie zdefiniowałem zmienną globalną, która była używana w pierwszej klasie stanowej, a kiedy przechodzę do drugiego widgetu stanowego, pozwoliłem jej zaktualizować wartość globalnej zmienna, która automatycznie wymusza aktualizację pierwszego widżetu. Oto przykład (napisałem go w pośpiechu, więc nie umieściłem rusztowania ani aplikacji materiałowej, chciałem tylko zilustrować mój punkt widzenia):
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++ ; }, ), }
źródło
Navigator.pop()
to zachować stan sprzedNavigator.push()
momentusetState
nazywa się ponownie, co nie zdarza się w tym kodzie. Czy coś mi brakuje?Ten prosty kod zadziałał, aby przejść do katalogu głównego i ponownie załadować stan:
... onPressed: () { Navigator.of(context).pushNamedAndRemoveUntil('/', ModalRoute.withName('/')); }, ...
źródło
Krótko mówiąc, powinieneś sprawić, by widżet obserwował stan. Potrzebujesz do tego zarządzania stanem.
Moja metoda jest oparta na dostawcy opisanym w przykładach architektury Flutter oraz Flutter Docs . Zapoznaj się z nimi, aby uzyskać bardziej zwięzłe wyjaśnienia, ale mniej więcej kroki są następujące:
Możesz mieć wiele stanów powiedzieć
data
iisLoading
poczekać na jakiś proces API. Sam model się rozciągaChangeNotifier
.Może to być
Consumer
lubSelector
.Dla modelu stanu klasa wyglądałaby mniej więcej następująco. Zwróć uwagę,
notifyListeners
który rozgłasza zmiany.class DataState extends ChangeNotifier{ bool isLoading; Data data; Future loadData(){ isLoading = true; notifyListeners(); service.get().then((newData){ isLoading = false; data = newData; notifyListeners(); }); } }
Teraz do widgetu. To będzie bardzo szkieletowy kod.
return ChangeNotifierProvider( create: (_) => DataState()..loadData(), child: ...{ Selector<DataState, bool>( selector: (context, model) => model.isLoading, builder: (context, isLoading, _) { if (isLoading) { return ProgressBar; } return Container( child: Consumer<DataState>(builder: (context, dataState, child) { return WidgetData(...); } )); }, ), } );
Wystąpienie modelu stanu jest dostarczane przez ChangeNotifierProvider. Selektor i Konsument obserwują stany, odpowiednio dla
isLoading
idata
. Nie ma między nimi dużej różnicy, ale osobiście sposób ich użycia zależy od tego, co zapewniają ich konstruktorzy. Konsument zapewnia dostęp do modelu stanu, więc wywoływanieloadData
jest prostsze dla wszystkich widżetów znajdujących się bezpośrednio pod nim.Jeśli nie, możesz użyć
Provider.of
. Jeśli chcielibyśmy odświeżyć stronę po powrocie z drugiego ekranu, możemy zrobić coś takiego:await Navigator.push(context, MaterialPageRoute( builder: (_) { return Screen2(); )); Provider.of<DataState>(context, listen: false).loadData();
źródło
U mnie pracował:
... onPressed: (){pushUpdate('/somePageName');} ... pushUpdate (string pageName) async { //in the same class await pushPage(context, pageName); setState(() {}); } //--------------------------------------------- //general sub pushPage (context, namePage) async { await Navigator.pushNamed(context, namePage); }
W tym przypadku nie ma znaczenia, w jaki sposób wyskakujesz (przyciskiem w interfejsie użytkownika lub „wstecz” w systemie Android) aktualizacja zostanie wykonana.
źródło