当弹出页面时,强制刷新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个回答

154

这里有几件事情你可以做。@Mahi的回答是正确的,但可以更简洁,并且实际上使用push而不是showDialog,因为OP正要求使用push。这是一个使用Navigator.push的示例:

import 'package:flutter/material.dart';

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.green,
      child: Column(
        children: <Widget>[
          RaisedButton(
            onPressed: () => Navigator.pop(context),
            child: 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: Column(
        children: <Widget>[
          RaisedButton(
            child: Text("next"),
            onPressed: () async {
              final value = await Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => SecondPage()),
                ),
              );
              setState(() {
                color = color == Colors.white ? Colors.grey : Colors.white;
              });
            },
          ),
        ],
      ),
    );
  }
}

void main() => runApp(
      MaterialApp(
        builder: (context, child) => SafeArea(child: child),
        home: FirstPage(),
      ),
    );

然而,有另一种方法可以做到这一点,可能更适合您的用例。如果您使用全局变量作为影响第一页构建的元素,您可以使用InheritedWidget来定义全局用户首选项,并且每次更改时,您的FirstPage都将重新构建。如下所示,即使在无状态小部件中也可以正常工作(但在有状态小部件中也应该可以)。

Flutter中InheritedWidget的一个示例是应用程序的主题,尽管它们在小部件内定义它而不是像我这里直接构建。

import 'package:flutter/material.dart';
import 'package:meta/meta.dart';

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.green,
      child: Column(
        children: <Widget>[
          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(),
      ),
    );

如果您使用继承的小部件,您就不必担心观察您推送的页面的弹出,这将适用于基本用例,但在更复杂的情况下可能会出现问题。


嘿,如果我想要更新我的列表项怎么办?假设我的第一页包含列表项,第二页包含项目详细信息。我正在更新项目的值,并希望将其更新回列表项中。如何实现这一点? - Aanal Shah
@AanalMehta 我可以给你一个快速的建议 - 要么有一个后端存储(即 sqflite 表或内存列表),两个页面都使用它,在 .then 中,你可以强制刷新你的小部件(我个人之前用过一个递增计数器,在 setState 中更改 - 这不是最干净的解决方案,但它有效)... 或者,在 .then 函数中将更改传回原始页面,对列表进行修改,然后重建(但请注意,对列表进行更改不会触发刷新,因此再次使用递增计数器)。 - rmtmckenzie
@AanalMehta 如果这不能帮助你,我建议你寻找其他可能更相关的答案(我相当确定我已经看到了一些关于列表等的答案),或者提出一个新问题。 - rmtmckenzie
1
@Panagiss 这可能是现在的推荐做法,但这个问题和答案来自2018年,早在不可变类成为Dart一部分或推荐使用之前。 - rmtmckenzie
我的Riverpod水平还很有限,但如果我没记错的话,我们也可以使用Riverpod提供者来解决这个问题,对吗? - undefined
显示剩余11条评论

94

简短回答:

在第一页使用此内容:

Navigator.pushNamed(context, '/page2').then((_) => setState(() {}));

并且这是在第二页:

Navigator.pop(context);

有两件事情,将数据从

  • 第一页传递到第二页

    在第一页使用以下代码:

      // 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");

在第一页使用这个,它与之前使用的相同,但稍作修改。

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

1
使用 Navigator.popUntil 方法从路由 C 弹出时,如何重新加载路由 A? - Vinoth Vino
1
@VinothVino 目前还没有直接的方法来做到这一点,你需要做一些变通处理。 - CopsOnRoad
在活动TabBar中,通过Navigator.pushreplacement从对话框提交按钮单击调用第二个选项卡,以重定向到第二个选项卡。@CopsOnRoad - s.j
不知道何时调用pop函数? - alex
@alex 你需要从第二个屏幕调用 Navigator.pop(...) - 这是你想要返回并返回结果的屏幕。 - CopsOnRoad

46

对我来说,这似乎有效:

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

那么在子组件中,只需要调用Navigator.pop()


这只能起作用一次,第二次返回相同的路由并进行更改时,弹出不会刷新。 - Boss Nass
我没有注意到这种行为,但是自从一年前就没有使用过Flutter了。如果其他人也观察到了这一点,我会很高兴听到的! - marre

26

使用Navigator.pushReplacement方法的简单技巧。

第一页

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

第2页

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

16
这样做会导致导航栈丢失。用返回按钮弹出屏幕怎么样? - encubos

17

只需在page1()中的Navigator.push之后添加.then((value) { setState(() {});,就像下面这样:

Navigator.push(context,MaterialPageRoute(builder: (context) => Page2())).then((value) { setState(() {});

当你在第二个页面使用 Navigator.pop(context) 后,第一个页面会重新构建。


15
onTapFunction(BuildContext context) async {
    final reLoadPage = await Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => IdDetailsScreen()),
    );

    if (reLoadPage) {
        setState(() {});
    }
}

现在,在从第二页通过Navigator.pop返回到第一页时,只需返回一些值,这些值在我的情况下是bool类型。

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

正是我所需要的,因为如果我使用 .then,返回类型将是 bool,因此我将无法调用 setState - Opeyemi Sanusi

12

您可以使用pushReplacement并指定新的路线。


13
但你正在失去导航栈。如果使用Android返回键弹出屏幕,会发生什么? - encubos
即使是使用pushReplacement,如果之前已经加载过上下文和状态,它也不会重新构建。我猜。 - MohitC

11

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

FirstPage

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

在弹出窗口后,所有需要从先前页面重新加载的内容都应该更新。


11

这份工作效果非常好,我从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(() {});
    }
  }

所以,我在墨水井中调用它的操作方法,但也可以从按钮中调用:

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

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

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

1
你可以直接使用 await Navigator.... 命令,并在pop操作中省略结果值。 - 0llie

8

我遇到了类似的问题。

请尝试以下方法:

在第一个页面中:

Navigator.push( context, MaterialPageRoute( builder: (context) => SecondPage()), ).then((value) => setState(() {}));

当你从SecondPage()返回到FirstPage()时,“then”语句将运行并刷新页面。


1
简单而优雅 - vishalknishad
易于实现...非常感谢 - Gvs Akhil

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