Tengo uno StatefulWidget
en Flutter con botón, que me lleva a otro StatefulWidget
usando Navigator.push()
. En el segundo widget, estoy cambiando el estado global (algunas preferencias del usuario). Cuando vuelvo del segundo widget al primero, Navigator.pop()
el primer widget está en estado antiguo, pero quiero forzar su recarga. ¿Alguna idea de cómo se hace esto? Tengo una idea pero se ve fea:
- pop para eliminar el segundo widget (el actual)
- pop de nuevo para eliminar el primer widget (el anterior)
- empujar el primer widget (debería forzar el redibujo)
Respuestas:
Hay un par de cosas que podrías hacer aquí. La respuesta de @ Mahi, aunque correcta, podría ser un poco más concisa y, en realidad, usar push en lugar de showDialog como preguntaba el OP. Este es un ejemplo que usa
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(), ), );
Sin embargo, hay otra forma de hacer esto que podría adaptarse bien a su caso de uso. Si está usando el
global
como algo que afecta la construcción de su primera página, puede usar un InheritedWidget para definir sus preferencias de usuario globales, y cada vez que se modifiquen, su FirstPage se reconstruirá. Esto incluso funciona dentro de un widget sin estado como se muestra a continuación (pero también debería funcionar en un widget con estado).Un ejemplo de heritageWidget en flutter es el tema de la aplicación, aunque lo definen dentro de un widget en lugar de tenerlo directamente compilado como lo he hecho aquí.
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(), ), );
Si usa un widget heredado, no tiene que preocuparse por ver la ventana emergente de la página que presionó, que funcionará para casos de uso básicos, pero puede terminar teniendo problemas en un escenario más complejo.
fuente
Hay dos cosas, pasar datos de
1ra página a 2da
Use esto en la primera página
// sending "Foo" from 1st Navigator.push(context, MaterialPageRoute(builder: (_) => Page2("Foo")));
Use esto en la segunda página.
class Page2 extends StatelessWidget { final String string; Page2(this.string); // receiving "Foo" in 2nd ... }
2da página a 1ra
Use esto en la segunda página
// sending "Bar" from 2nd Navigator.pop(context, "Bar");
Use esto en la 1ra página, es el mismo que se usó anteriormente pero con pocas modificaciones.
// receiving "Bar" in 1st String received = await Navigator.push(context, MaterialPageRoute(builder: (_) => Page2("Foo")));
fuente
Puede utilizar pushReplacement y especificar la nueva ruta
fuente
El truco fácil es utilizar el método Navigator.pushReplacement
Página 1
Página 2
fuente
Este trabajo es realmente bueno, lo obtuve de este documento de la página de flutter : flutter doc
Definí el método para controlar la navegación desde la primera página.
_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(() {}); } }
Entonces, lo llamo en un método de acción desde un tintero, pero también se puede llamar desde un botón:
Y finalmente en la segunda página, para devolver algo (devolví un bool, puedes devolver lo que quieras):
onTap: () { Navigator.pop(context, true); }
fuente
await Navigator....
y omitir un valor de resultado en pop.Coloque esto donde está presionando a la segunda pantalla (dentro de una función asincrónica)
Function f; f= await Navigator.pushNamed(context, 'ScreenName'); f();
Pon esto donde estás haciendo estallar
Se
setState
llama dentro delpop
cierre para actualizar los datos.fuente
setState
como argumento? Básicamente estás llamandosetState
dentro de un cierre. No pasarlo como argumento.Navigator.pop
.mi solución fue agregando un parámetro de función en SecondPage, luego recibió la función de recarga que se está haciendo desde FirstPage, luego ejecutó la función antes de la línea Navigator.pop (contexto).
Primera página
refresh() { setState(() { //all the reload processes }); }
luego, al pasar a la página siguiente ...
Navigator.push(context, new MaterialPageRoute(builder: (context) => new SecondPage(refresh)),);
Segunda pagina
final Function refresh; SecondPage(this.refresh); //constructor
luego, antes de la línea emergente del navegador,
widget.refresh(); // just refresh() if its statelesswidget Navigator.pop(context);
Todo lo que necesita ser recargado desde la página anterior debe actualizarse después del pop.
fuente
Puede devolver un
dynamic result
cuando está abriendo el contexto y luego llamar alsetState((){})
cuando el valor es; de lotrue
contrario, deje el estado como está.He pegado algunos fragmentos de código para su referencia.
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; }
Saludos, Mahi
fuente
Navigator.pop(context, true)
, ¿verdad? Pero, ¿cómo puedo obtener estetrue
valor? UsandoBuildContext
?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;
antes de cada llamada desetState((){});
esta manera puede evitar actualizar los widgets eliminados o los widgets que ya no están activos.Necesitaba forzar la reconstrucción de uno de mis widgets sin estado. No quería usar stateful. Se me ocurrió esta solución:
await Navigator.of(context).pushNamed(...); ModalRoute.of(enclosingWidgetContext);
Tenga en cuenta que el contexto y el WidgetContext adjunto podrían ser el mismo o diferentes contextos. Si, por ejemplo, empuja desde dentro de StreamBuilder, serían diferentes.
No hacemos nada aquí con ModalRoute. El solo hecho de suscribirse es suficiente para forzar la reconstrucción.
fuente
Si está utilizando un cuadro de diálogo de alerta, puede usar un Futuro que se completa cuando se cierra el cuadro de diálogo. Después de la finalización del futuro, puede forzar al widget a recargar el estado.
Primera página
onPressed: () async { await showDialog( context: context, builder: (BuildContext context) { return AlertDialog( .... ); } ); setState(() {}); }
En el diálogo de alerta
fuente
Hoy me enfrenté a la misma situación pero logré resolverla de una manera mucho más fácil, acabo de definir una variable global que se usó en la primera clase con estado, y cuando navego a un segundo widget con estado dejo que actualice el valor del global variable que fuerza automáticamente la actualización del primer widget. Aquí hay un ejemplo (lo escribí con prisa, así que no puse un andamio o una aplicación de material, solo quería ilustrar mi punto):
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++ ; }, ), }
fuente
Navigator.pop()
, mantendrá el estado anteriorNavigator.push()
hasta quesetState
se vuelva a llamar, lo que no sucede en este código. ¿Me estoy perdiendo de algo?Este código simple me funcionó para ir a la raíz y recargar el estado:
... onPressed: () { Navigator.of(context).pushNamedAndRemoveUntil('/', ModalRoute.withName('/')); }, ...
fuente