弹出时强制Flutter导航器重新加载状态


108

我有一个StatefulWidget在颤振按钮,导航我到另一个StatefulWidget使用Navigator.push()。在第二个小部件上,我正在更改全局状态(某些用户首选项)。当我从第二个窗口小部件返回到第Navigator.pop()一个窗口小部件时,使用第一个窗口小部件处于旧状态,但是我想强制重新加载它。任何想法如何做到这一点?我有一个主意,但看起来很丑:

  1. 弹出以删除第二个小部件(当前一个)
  2. 再次弹出以删除第一个小部件(上一个)
  3. 推送第一个小部件(应强制重绘)

1
没有答案,只是一个一般性的评论:就我而言,带我到这里寻找答案的方法是通过对shared_preferences使用同步方法来解决,在该方法中,我可以保证取回我刚才在另一页中写的更新的首选项。 。:\即使使用.then(...)也不总是让我得到挂起的更新数据。
ChrisH '20

刚从弹出的新页面返回一个值,解决了我的问题。请参阅flutter.dev/docs/cookbook/navigation/returning-data
Joe M

Answers:


85

您可以在这里做几件事。@Mahi的正确答案可能会更简洁一些,实际上是在OP询问时使用push而不是showDialog。这是一个使用示例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(),
      ),
    );

但是,还有另一种方法可以很好地满足您的用例。如果您使用global会影响首页的构建,则可以使用InheritedWidget定义全局用户首选项,并且每次更改它们时,FirstPage都会重新生成。甚至可以在如下所示的无状态小部件中使用(但也应在有状态小部件中使用)。

应用程序的主题是flutter中的InheritedWidget的一个示例,尽管它们是在窗口小部件中定义的,而不是像我在此处那样直接构建它。

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(),
      ),
    );

如果您使用继承的小部件,则不必担心要查看所推送页面的弹出状态,该弹出窗口适用于基本用例,但在更复杂的情况下最终可能会遇到问题。


太好了,第一个案例非常适合我(第二个案例对于更复杂的情况非常理想)。我不清楚从第二页开始使用OnWillPopUp。甚至根本没有工作。
cdsaenz

嘿,如果我要更新列表项,该怎么办。假设我的第一页包含列表项,第二页包含项详细信息。我正在更新项目的值,并且希望它在列表项中更新。如何实现呢?
阿纳拉·梅塔

@AanalMehta我可以给您一个快速的建议-在两个页面中都使用一个后端存储(即sqflite表或内存列表),然后在.then中,然后可以强制您的小部件以某种方式刷新(我个人)曾经使用过我在setState中更改过的递增计数器-这不是最干净的解决方案,但是它可以工作)...或者,您可以将更改传递回.then函数中的原始页面,对列表进行修改,然后然后重建(但请注意,对列表进行更改不会触发刷新,因此请再次使用递增计数器)。
rmtmckenzie

@AanalMehta但是,如果这样做没有帮助,我建议您寻找其他可能更相关的答案(我很确定我已经看过一些有关列表的信息),或者提出一个新问题。
rmtmckenzie

@rmtmckenzie实际上,我尝试了上述一种解决方案。我在.then中应用了更改,但不会得到体现。我想现在我只有一种选择可以使用提供程序。无论如何,非常感谢您的帮助。
阿纳尔·梅塔

20

有两件事,从

  • 第一页到第二页

    在第一页中使用

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

    在第二页中使用它。

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

  • 第二页到第一页

    在第二页中使用此

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

    在第1页中使用它,它与以前使用的相同,但几乎没有修改。

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

使用Navigator.popUntil方法从路线C弹出时,如何重新加载路线A。
Vinoth Vino

1
@VinothVino尚无直接方法,您需要采取某种解决方法。
CopsOnRoad

活动TabBar中的@CopsOnRoad可以从对话框的提交按钮中调用第二个选项卡,然后单击Navigator.pushreplacement以重定向到第二个选项卡。
sj

13

简易窍门是使用Navigator.pushReplacement方法

第1页

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

第2页

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

这样,您将丢失导航器堆栈。使用后退按钮弹出屏幕呢?
encubos


5

这项工作真的很好,我从flutter页面的以下文档中获得了信息:flutter doc

我定义了从首页控制导航的方法。

_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(() {});
    }
  }

因此,我在墨水池的action方法中调用它,但也可以从按钮中调用它:

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

最后在第二页中,返回一些内容(我返回了布尔值,您可以返回任何想要的值):

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

1
您甚至可以公正地await Navigator....忽略pop中的结果值。
0llie

5

对我来说,这似乎可行:

Navigator.of(context).pushNamed("/myRoute").then((value) => setState(() {}));

然后简单地叫Navigator.pop()孩子。


4

将其放在您要推送到第二个屏幕的位置(在异步功能内部)

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

把它放在你弹出的地方

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

setState被称为内部pop封闭更新数据。


2
您到底在哪里setState争论?您基本上是setState在闭包内调用。不将其作为参数传递。
VolkanGüven20年

这是我一直在寻找的确切答案。我基本上想要的完成处理程序Navigator.pop
大卫·肖邦

3

我的解决方案是在SecondPage上添加一个函数参数,然后接收从FirstPage完成的重载函数,然后在Navigator.pop(context)行之前执行该函数。

第一页

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

然后推到下一页...

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

第二页

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

然后在导航器弹出行之前

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

弹出后,应更新需要从上一页重新加载的所有内容。


1

您可以dynamic result在弹出上下文时传递回a ,然后setState((){})在值是时调用,true否则只需保留状态即可。

我粘贴了一些代码片段供您参考。

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

问候,Mahi


我不确定我是否理解第二部分。首先就是简单Navigator.pop(context, true)吧?但是我如何获得这个true价值?使用BuildContext
bartektartanus

不为我工作。我已经使用了推的结果,称为setState,得到了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. ....
bartektartanus 18'Apr

嗯,这很有趣,您if(!mounted) return;在每次调用setState((){});此方法之前都使用过,可以避免更新已处置的小部件或不再处于活动状态的小部件。
马希

没有错误,但它也不如我所预期的那样工作;)
bartektartanus 18-4-12

我有与@bartektartanus相同的问题。如果我在setState之前添加!mount,则将永远不会设置我的状态。看起来很简单,但是几个小时后我还没有弄清楚。
Swift

1

需要强制重建我的无状态小部件之一。不想使用有状态。提出了以下解决方案:

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

注意context和enclosingWidgetContext可以是相同或不同的上下文。例如,如果您从StreamBuilder内部推送,它们将有所不同。

我们在这里不使用ModalRoute做任何事情。单独订阅的行为足以强制重建。


1

如果您使用的是警报对话框,则可以使用在关闭对话框时完成的Future。将来完成后,您可以强制窗口小部件重新加载状态。

第一页

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

在警报对话框中

Navigator.of(context).pop();

0

今天,我遇到了同样的情况,但是我设法以更简单的方式解决了这个问题,我只定义了一个在第一个有状态类中使用的全局变量,当我导航到第二个有状态小部件时,我让它更新了全局值变量,它会自动强制第一个小部件进行更新。这是一个示例(我匆忙编写了它,所以我没有放置脚手架或材料应用程序,我只是想说明我的观点):

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
更改变量不会自动重建窗口小部件。之后Navigator.pop(),它将保持状态Navigator.push()直到setState再次被调用之前,此代码中不会发生什么。我想念什么吗?
里卡多BRGWeb '19

0

这个简单的代码对我有用,它可以进入根目录并重新加载状态:

    ...
    onPressed: () {
         Navigator.of(context).pushNamedAndRemoveUntil('/', ModalRoute.withName('/'));
                },
    ...

0

简而言之,您应该使小部件监视状态。您需要为此进行状态管理。

我的方法基于Flutter体系结构示例以及Flutter Docs中所述的Provider 。请参考它们以获得更简洁的说明,但步骤大致如下:

  • 使用小部件需要观察的状态来定义状态模型。

您可能有多个状态,例如dataisLoading,以等待某些API进程。模型本身会扩展ChangeNotifier

  • 使用watcher类包装依赖于那些状态的小部件。

这可能是ConsumerSelector

  • 当您需要“重新加载”时,基本上可以更新这些状态并广播更改。

对于状态模型,该类大致如下所示。注意notifyListeners哪个广播更改。

class DataState extends ChangeNotifier{

  bool isLoading;
  
  Data data;

  Future loadData(){
    isLoading = true;
    notifyListeners();

    service.get().then((newData){
      isLoading = false;
      data = newData;
      notifyListeners();
    });
  }
  
}

现在为小部件。这将是非常简单的代码。

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(...);

              }
          ));
        },
      ),
  }
);

状态模型的实例由ChangeNotifierProvider提供。选择器和消费者监视状态,分别为isLoadingdata。有他们之间没有太大的区别,但个人如何使用它们将依靠什么他们的建设者提供。消费者提供对状态模型的访问,因此loadData对于直接在其下的任何小部件调用都更加简单。

如果没有,则可以使用Provider.of。如果我们想在从第二个屏幕返回时刷新页面,则可以执行以下操作:

await Navigator.push(context, 
  MaterialPageRoute(
    builder: (_) {
     return Screen2();
));

Provider.of<DataState>(context, listen: false).loadData();


0

对我来说工作:

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

在这种情况下,您的弹出方式(用户界面中的按钮或android中的“后退”)都无关紧要,更新将完成。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.