Flutter - 通过 .popUntil 返回数据

29

我一直很容易地使用Navigator.pop返回数据到上一个屏幕。然而,如果我使用Navigator.popUntil,有什么可接受的方法将一个对象传回目标屏幕?


我已经回答了这个问题,这可能会对你有所帮助 https://dev59.com/r1QK5IYBdhLWcg3wZvAs#51928001 - Sher Ali
2
@Zulfiqar,看起来你正在将数据发送到上一级。但要求是向多个级别传递。 - Dinesh Balasubramanian
你想要返回多少个屏幕?你想传递多大(或什么类型的)数据?不要给出“通用”的答案,根据上述问题的答案考虑一些解决方法。 - Dinesh Balasubramanian
看看这个是否有帮助 https://dev59.com/r1QK5IYBdhLWcg3wZvAs#54455666 - Deepak Thakur
5个回答

26
你可以在RouteSettings中使用arguments将数据传递回特定的Route
例如:

// in MaterialApp
MaterialApp(
   onGenerateRoute: (settings) {
      switch (settings.name) {
         case '/':
            return MaterialPageRoute(
                     settings: RouteSettings(name: '/', arguments: Map()),  // (1)
                     builder: (_) => HomePage()
                  );
      }
   }
)

// in HomePage
Navigator.of(context).push(MaterialPageRoute(builder: (_) => StuffPage())
                     .then(_) {
                        final arguments = ModalRoute.of(context).settings.arguments as Map;
                        final result = arguments['result'];
                     };

// in StuffPage
Navigator.of(context).popUntil((route) {
   if (route.settings.name == '/') {
      (route.settings.arguments as Map)['result'] = 'something';
      return true;
   } else {
      return false;
   }
});

请注意:您必须初始化arguments,否则它将为null,这就是(1)的目的。


这必须是一个被接受的答案。我认为它在任何情况下都能工作。干得好,@hunghd! - Jay Mungara
3
RouteSettings(name: '/', arguments: Map()) 这是最重要的代码行。 - AJ Seraspi
1
我需要在弹出后清除参数。有什么方法可以做到这一点吗? - humblePilgrim
当代码中出现 popUntil 时,它将一直执行到定义屏幕的下一个步骤。我正在收到 null 值。.then((value) { print(value); }); - ashutosh
不适用于单次行为。在使用 result 参数后,应该通过 (ModalRoute.of(context)!.settings.arguments as Map).clear() 清除地图。 - Michał Dobi Dobrzański
在这一行 (route.settings.arguments as Map)['result'] = 'something'; 中显示错误:List<Object?> 不是类型为 Map<dynamic, dynamic> 的子类型。 - Moony-Stary

2

这是不可能的。而且也没有意义,因为结果应该返回到推送路由的路由。

如果你真的必须这样做,使用多个pop调用:

// Widget of Route A:
String resultOfC = await Navigator.push(context, routeB);


// Widget of Route B:
String resultOfC = await Navigator.push<String>(context, routeC);
Navigator.pop(context, resultOfC); // pass result of C to A


// Widget of Route C:
Navigator.pop(context, 'my result'); // pass result to B

这将在视图中显示,而popUntil不会。因此,如果我们能够弹出并传递数据,那就更好了。 - Dinesh Balasubramanian
是的,还有其他方法。我想知道 OP 正在尝试做什么。他使用 Navigator 的方式可能存在概念问题。 - boformer
1
这似乎是最干净的方法,尽管它会导致紧密耦合。我认为耦合不是问题,因为它只有两层深度,在大多数应用程序中可能不经常发生。如果你必须要回到三层,我会质疑这种方法是否太笨重了。 - AutoM8R

0

加上 @hunghd 答案 确保你将要弹回的 屏幕设置 不能有一个 const 键。否则你将会面临以下错误:

Unhandled Exception: Unsupported operation: Cannot modify unmodifiable map

我刚刚从以下代码的设置中删除了const关键字,用户将被弹回。

 case HomeViewRoute:
      return MaterialPageRoute(
        builder: (context) => HomePage(),
        settings: RouteSettings(name: HomeViewRoute, arguments:  {}),
      );

0

这里是关于单次参数读取的解决方案。由于@hunghd提出的解决方案没有考虑到这一点。这里的诀窍是只消耗参数一次,而不是多次,因为这可能会导致错误行为。

如果我们想要“用单个结果返回”,我们应该执行以下操作。我们可以通过三个步骤来起草以下引擎:

第一步-在Main.app中进行设置:

MaterialApp(
   onGenerateRoute: (settings) {
       switch (settings.name) {
           case Routes.HOME: // Or your other path, like "/"
               return generateRouteWithMapArguments(
                   Routes.HOME,
                   HomePage(), // A page Widget
               );
       // Other switch cases
      }
   }
)

// util:
static MaterialPageRoute generateRouteWithMapArguments(
    String routeName,
    Widget page,
  ) =>
      MaterialPageRoute(
        settings: RouteSettings(name: routeName, arguments: Map()),
        builder: (context) => page,
      );

第二步 - 进行导航:

const String ARGUMENTS_REMOVED = "ARGUMENTS_REMOVED";


void _navigateToDetails(BuildContext context) async {
    // navigate here help with the help of Navigator.of(context).push() or GetX
    await Navigator.of(context).push(...)
    var result = ArgsUtils.consumeArgument(
      context,
      ARGUMENTS_REMOVED,
      () {
        // HANDLE SINGLE SHOT HERE
      },
    );
  }

// utils:
class ArgsUtils {
  static T readArgs<T>(BuildContext context) {
    return ModalRoute.of(context)!.settings.arguments as T;
  }

  static bool consumeArgument(
    BuildContext context,
    String argument,
    VoidCallback onArgument,
  ) {
    var map = readArgs<Map>(context);
    if (map.containsKey(argument)) {
      onArgument.call();
      map.clear();
      return true;
    }
    return false;
  }
}

*第三步 - 返回参数:

// passing back. Call this method two pages after navigating deeper from HOME 
NavigatorUtils.popTwoPagesWithArgument(
    context,
    routeName: Routes.HOME,
    argument: ARGUMENTS_REMOVED,
);


// util:
class NavigatorUtils {
  // you can pop more pages here or add different predicate:
  static void popTwoPagesWithArgument(
    final BuildContext context, {
    required final String routeName,
    required final String argument,
  }) {
    int count = 0;
    Navigator.of(context).popUntil((route) {
      if (route.settings.name == routeName &&
          route.settings.arguments != null) {
        (route.settings.arguments! as Map)[argument] = true;
      }
      return count++ >= 2;
    });
  }
}

当然,这个解决方案传递了冗余的bool。这引发了问题,是否需要一个Map。对于单次使用情况,我认为可以通过使用Set<String>而不是Map来改进。

-1

在Flutter团队提供解决方案之前,您可以使用shared_preferences来实现。

  1. 添加一个类和函数,用于获取和保存值到shared_preferences中。
  static const isBackFromPopUntil = "isBackFromPopUntil";

  Future<void> saveIsBackFromPopUntil(bool isBack) async =>
      await preferences.setBool(isBackFromPopUntil, isBack);

  bool getIsBackFromPopUntil() =>
      preferences.getBool(isBackFromPopUntil) ?? false;

例如,当模态底部表单从popUntil关闭时,您需要检查“某些内容”,否则不需要。
  void _openBottomSheet(BuildContext context) async {
    await showCupertinoModalBottomSheet(
      context: context,
      isDismissible: true,
      ...
    );
    final isBack = _prefHelper.getIsBackFromPopUntil();
    if (isBack) {
      // do stuff
      await _prefHelper.saveIsBackFromPopUntil(false);
    }
  }
  1. 你可以使用 popUntil 返回到之前的屏幕。
void _popUntilDetailSavingPlan(BuildContext context) async {
    final routeName = 'your name route that have bottom sheet (in the number 2 above)';
    await _prefHelper.saveIsBackFromPopUntil(true);
    Navigator.popUntil(context, ModalRoute.withName(routeName));
  }

这是解决问题的一种非常糟糕的方法。 - Alberto M

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