Tenho um StatefulWidget
no Flutter com botão, que me navega para outro StatefulWidget
usando Navigator.push()
. No segundo widget, estou mudando o estado global (algumas preferências do usuário). Quando volto do segundo widget para o primeiro, o uso Navigator.pop()
do primeiro widget está no estado antigo, mas quero forçar a recarga. Alguma idéia de como fazer isso? Eu tenho uma ideia, mas parece feia:
- pop para remover o segundo widget (o atual)
- pop novamente para remover o primeiro widget (anterior)
- empurre o primeiro widget (deve forçar o redesenho)
Respostas:
Há algumas coisas que você pode fazer aqui. A resposta de @Mahi enquanto correta poderia ser um pouco mais sucinta e realmente usar push em vez de showDialog, como o OP estava perguntando. Este é um exemplo 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(), ), );
No entanto, há outra maneira de fazer isso que pode se adequar bem ao seu caso de uso. Se estiver usando o
global
como algo que afeta a construção de sua primeira página, você pode usar um InheritedWidget para definir suas preferências globais de usuário e, cada vez que elas forem alteradas, sua FirstPage será reconstruída. Isso funciona até mesmo em um widget sem estado, conforme mostrado abaixo (mas também deve funcionar em um widget com estado).Um exemplo de inheritedWidget em flutter é o tema do aplicativo, embora eles o definam em um widget em vez de construí-lo diretamente, como fiz aqui.
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(), ), );
Se você usar o widget herdado, não precisará se preocupar em ficar atento ao pop da página que enviou, o que funcionará para casos de uso básicos, mas pode acabar tendo problemas em um cenário mais complexo.
fonte
Existem 2 coisas, passando dados de
1ª página para 2ª
Use isto na primeira página
// sending "Foo" from 1st Navigator.push(context, MaterialPageRoute(builder: (_) => Page2("Foo")));
Use isto na 2ª página.
class Page2 extends StatelessWidget { final String string; Page2(this.string); // receiving "Foo" in 2nd ... }
2ª página à 1ª
Use isto na 2ª página
// sending "Bar" from 2nd Navigator.pop(context, "Bar");
Use isto na primeira página, é o mesmo que foi usado anteriormente, mas com poucas modificações.
// receiving "Bar" in 1st String received = await Navigator.push(context, MaterialPageRoute(builder: (_) => Page2("Foo")));
fonte
O truque fácil é usar o Navigator.pushReplacement método
Página 1
Página 2
fonte
Você pode usar pushReplacement e especificar a nova rota
fonte
Este trabalho é muito bom, eu peguei deste documento da página flutter : flutter doc
Eu defini o método para controlar a navegação da primeira 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(() {}); } }
Então, eu o chamo em um método de ação de um tinteiro, mas também pode ser chamado de um botão:
E finalmente na segunda página, para retornar algo (eu retornei um bool, você pode retornar o que quiser):
onTap: () { Navigator.pop(context, true); }
fonte
await Navigator....
omitir um valor de resultado no pop.Para mim, isso parece funcionar:
Navigator.of(context).pushNamed("/myRoute").then((value) => setState(() {}));
Em seguida, basta chamar
Navigator.pop()
a criança.fonte
Coloque isso onde você está empurrando para a segunda tela (dentro de uma função assíncrona)
Function f; f= await Navigator.pushNamed(context, 'ScreenName'); f();
Coloque isso onde você está colocando
O
setState
é chamado dentro dopop
fechamento para atualizar os dados.fonte
setState
como argumento? Você está basicamente ligandosetState
dentro de um encerramento. Não passando isso como um argumento.Navigator.pop
.minha solução foi adicionar um parâmetro de função no SecondPage, depois receber a função de recarregamento que está sendo feita no FirstPage e executar a função antes da linha Navigator.pop (contexto).
Primeira página
refresh() { setState(() { //all the reload processes }); }
em seguida, avançando para a próxima página ...
Navigator.push(context, new MaterialPageRoute(builder: (context) => new SecondPage(refresh)),);
SecondPage
final Function refresh; SecondPage(this.refresh); //constructor
em seguida, antes da linha pop do navegador,
widget.refresh(); // just refresh() if its statelesswidget Navigator.pop(context);
Tudo o que precisa ser recarregado da página anterior deve ser atualizado após o pop.
fonte
Você pode devolver a
dynamic result
quando estiver abrindo o contexto e, em seguida, chamarsetState((){})
quando o valor for,true
caso contrário, apenas deixe o estado como está.Eu colei alguns trechos de código para sua referência.
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; }
Atenciosamente, Mahi
fonte
Navigator.pop(context, true)
, certo? Mas como posso obter essetrue
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 chamada parasetState((){});
evitar a atualização dos widgets descartados ou que não estão mais ativos.Necessário para forçar a reconstrução de um dos meus widgets sem estado. Não queria usar stateful. Surgiu com esta solução:
await Navigator.of(context).pushNamed(...); ModalRoute.of(enclosingWidgetContext);
Observe que context e enclosingWidgetContext podem ser contextos iguais ou diferentes. Se, por exemplo, você enviar de dentro do StreamBuilder, eles serão diferentes.
Não fazemos nada aqui com o ModalRoute. O ato de assinar sozinho é suficiente para forçar a reconstrução.
fonte
Se estiver usando uma caixa de diálogo de alerta, você pode usar um Futuro que é concluído quando a caixa de diálogo é descartada. Após a conclusão do futuro, você pode forçar o widget a recarregar o estado.
Primeira página
onPressed: () async { await showDialog( context: context, builder: (BuildContext context) { return AlertDialog( .... ); } ); setState(() {}); }
Na caixa de diálogo de alerta
fonte
Hoje enfrentei a mesma situação, mas consegui resolvê-la de uma maneira muito mais fácil, apenas defini uma variável global que foi usada na primeira classe com estado, e quando navego para um segundo widget com estado, deixo ele atualizar o valor do global variável que força automaticamente o primeiro widget a ser atualizado. Aqui está um exemplo (eu o escrevi com pressa, então não coloquei um andaime ou um aplicativo de material, eu só queria ilustrar meu ponto):
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++ ; }, ), }
fonte
Navigator.pop()
ele manterá o estado antesNavigator.push()
atésetState
ser chamado novamente, o que não acontece neste código. Estou esquecendo de algo?Este código simples funcionou para eu ir à raiz e recarregar o estado:
... onPressed: () { Navigator.of(context).pushNamedAndRemoveUntil('/', ModalRoute.withName('/')); }, ...
fonte
Resumindo, você deve fazer o widget observar o estado. Você precisa de gerenciamento de estado para isso.
Meu método é baseado no Provider explicado em Flutter Architecture Samples e também em Flutter Docs . Consulte-os para uma explicação mais concisa, mas mais ou menos as etapas são:
Você poderia ter vários estados dizendo
data
eisLoading
, para aguardar algum processo de API. O próprio modelo se estendeChangeNotifier
.Isso pode ser
Consumer
ouSelector
.Para o modelo de estado, a classe seria mais ou menos como segue. Preste atenção em
notifyListeners
quem transmite as mudanças.class DataState extends ChangeNotifier{ bool isLoading; Data data; Future loadData(){ isLoading = true; notifyListeners(); service.get().then((newData){ isLoading = false; data = newData; notifyListeners(); }); } }
Agora, para o widget. Este será um código-esqueleto.
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(...); } )); }, ), } );
A instância do modelo de estado é fornecida por ChangeNotifierProvider. O Seletor e o Consumidor observam os estados, cada um para
isLoading
edata
respectivamente. Não há muita diferença entre eles, mas pessoalmente como você os usa depende do que seus construtores fornecem. O consumidor fornece acesso ao modelo de estado, assim chamandoloadData
é mais simples para quaisquer widgets diretamente abaixo dele.Se não, então você pode usar
Provider.of
. Se quisermos atualizar a página ao retornar da segunda tela, podemos fazer algo assim:await Navigator.push(context, MaterialPageRoute( builder: (_) { return Screen2(); )); Provider.of<DataState>(context, listen: false).loadData();
fonte
Para mim funcionou:
... 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); }
Neste caso, não importa como você pop (com o botão na IU ou "voltar" no android), a atualização será feita.
fonte