当弹出页面时,强制刷新Flutter导航器状态

240
在Flutter中,我有一个带有按钮的StatefulWidget,当我点击该按钮时,使用Navigator.push()导航到另一个StatefulWidget。在第二个widget中,我更改了全局状态(某些用户首选项)。当我从第二个widget返回到第一个widget时,使用Navigator.pop(),第一个widget仍然处于旧状态,但是我想强制重新加载它。有任何想法如何做到这一点?我有一个想法,但它看起来不好:

  1. pop以删除第二个widget(当前widget)
  2. 再次pop以删除第一个widget(之前的widget)
  3. 推送第一个widget(它应该强制重新绘制)

1
没有答案,只是一般性的评论:在我的情况下,我来到这里寻找解决方案的原因就是希望有一个同步方法用于 shared_preferences,在另一页中刚刚写入的更新后的偏好设置可以得到保证。即使使用 .then(...) 也不能总是获取到待处理的更新数据。 - ChrisH
刚刚在弹出的新页面上返回了一个值,解决了我的问题。请参考 https://flutter.dev/docs/cookbook/navigation/returning-data。 - Joe M
24个回答

6
把这个放在你将内容推送到第二个屏幕的位置(在异步函数内)。
Function f;
f= await Navigator.pushNamed(context, 'ScreenName');
f();

把这个放在你弹出的地方

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

setStatepop 闭包内被调用以更新数据。


2
你在哪里将setState作为参数传递?你基本上是在闭包内调用setState,而不是将其作为参数传递。 - knoxgon
1
这正是我在寻找的确切答案。我基本上想要一个Navigator.pop的完成处理程序。 - David Chopin

4
当你弹出上下文时,可以返回一个动态结果,然后在值为true时调用setState((){}),否则将状态保持不变。
以下是一些代码片段供您参考。
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
对我没用。我使用了push的结果,调用了setState并得到了setState() called after dispose():错误。 如果在不再出现在widget tree中(例如,其父widget不再将该widget包含在其build中)的widget的State对象上调用setState(),则会出现此错误。当代码从定时器或动画回调中调用setState()时,可能会出现此错误。首选解决方案是在dispose()回调中取消定时器或停止监听动画。... - bartektartanus
嗯,这很有趣。在每次调用 setState((){}); 之前,您是否使用了 if(!mounted) return;?这样可以避免更新已处理的小部件或不再活动的小部件。 - Mahi
没有错误,但它也不像我预测的那样工作 ;) - bartektartanus
我和@bartektartanus有同样的问题。如果在setState之前添加if !mounted,我的状态将永远不会被设置。这似乎很简单,但我已经花了几个小时还没有解决。 - Swift

2
在Flutter 2.5.2中,这对我也有效,它适用于更新列表。
Navigator.push(
        context,
        MaterialPageRoute(
            builder: (context) => SecondPage()))
    .then((value) => setState(() {}));

然后在第二页,我只编写了这个代码

Navigator.pop(context);

我在第一个页面有一个 ListView ,它显示了一个 list[] 数据,第二个页面更新了我的 list[] 数据,所以上面的代码对我有效。


2

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

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

请注意,context和enclosingWidgetContext可能是相同的上下文或不同的上下文。例如,如果您从StreamBuilder内部推送,则它们将是不同的。
在这里,我们不会对ModalRoute进行任何操作。仅订阅的行为就足以强制重建。

2

如果您正在使用警报对话框,那么可以使用一个Future,在对话框关闭后完成。在未来完成后,您可以强制小部件重新加载状态。

第一页

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

在警告对话框中

Navigator.of(context).pop();

1
// Push to second screen
 await Navigator.push(
   context,
   CupertinoPageRoute(
    builder: (context) => SecondScreen(),
   ),
 );

// Call build method to update any changes
setState(() {});

这并不是一个好的解决方案,因为Navigator.push会将当前页面添加到历史记录中,在弹出上下文的情况下(例如用户返回主页),它会再次带你回到同一页。 - Santiago

1

很简单,当你使用push后,在navigator弹回来时会触发setState并刷新视图,只需在push后加上"then"即可。

Navigator.push(blabla...).then((value) => setState(() {}))


1
对我而言有效的方法是:
...
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);
}

在这种情况下,无论您是通过UI中的按钮还是android中的“返回”弹出更新,都会完成更新。

1
这段简单的代码对我很有效,可以回到根目录并重新加载状态:
    ...
    onPressed: () {
         Navigator.of(context).pushNamedAndRemoveUntil('/', ModalRoute.withName('/'));
                },
    ...

1
简而言之,您应该使小部件观察状态。为此,您需要进行状态管理。
我的方法基于Flutter Architecture Samples中解释的Provider以及Flutter Docs。请参阅它们以获取更简洁的说明,但步骤或多或少如下:
- 使用小部件需要观察的状态定义您的状态模型。 - 您可以拥有多个状态,例如`data`和`isLoading`,以等待某些API过程。模型本身扩展了`ChangeNotifier`。
- 使用观察器类包装依赖于这些状态的小部件。 - 这可以是`Consumer`或`Selector`。
- 当您需要“重新加载”时,您基本上会更新这些状态并广播更改。
对于状态模型,该类看起来或多或少如下。请注意,`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提供。Selector和Consumer分别监视状态,每个状态为isLoadingdata。它们之间没有太大的区别,但是你如何使用它们取决于它们的构建器提供的内容。Consumer提供对状态模型的访问,因此对其直接下方的任何小部件调用loadData更简单。

如果不是这样,您可以使用Provider.of。如果我们想在从第二个屏幕返回时刷新页面,那么我们可以像这样做:

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接