I have been using Navigator.pop
and passing data back 1 screen with ease. However, if I use Navigator.popUntil
, what is an acceptable way to pass an object back to the destination screen?

- 27,060
- 21
- 118
- 148

- 16,765
- 45
- 140
- 253
-
I have already answer the question this may help you https://stackoverflow.com/questions/51927885/flutter-back-button-with-return-data/51928001#51928001 – Sher Ali Aug 29 '18 at 11:37
-
2@Zulfiqar It looks you are sending data to one level back. But the requirement is pass multiple level back – Dinesh Balasubramanian Aug 29 '18 at 12:39
-
How many screens do you want to go back? How big(or what kind of) the data you want to pass? Don't have a `generic` answer, thinking for some workaround based on the answers to the above questions. – Dinesh Balasubramanian Aug 29 '18 at 16:21
-
see if this helps https://stackoverflow.com/a/54455666/2033377 – Deepak Thakur Jan 31 '19 at 08:55
5 Answers
You can using arguments
in RouteSettings
to pass data back to a specific Route
.
For example:
// 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;
}
});
Note that: you have to initialize arguments
or it will be null, that's what is the purpose of (1)

- 1,274
- 11
- 21
-
This must be an accepted answer. It will work in each condition i think. Well done @hunghd – Jay Mungara Nov 12 '19 at 06:33
-
3`RouteSettings(name: '/', arguments: Map())` this one is the most important line of code – AJ Seraspi May 01 '20 at 02:28
-
1I need to clear the arguments once I have popped back. Any way to do that ? – humblePilgrim Oct 07 '20 at 09:02
-
When it says `popUntil` it will go till one step ahead of defined screen. I am getting value as null `.then((value) { print(value); });` – ashutosh Dec 14 '21 at 08:55
-
Does not work for single-shot behaviour. A map should be cleared after consuming the `result ` argument with: `(ModalRoute.of(context)!.settings.arguments as Map).clear()` – Michał Dobi Dobrzański Jan 17 '23 at 15:36
-
showing error in this ` (route.settings.arguments as Map)['result'] = 'something';` line`List – Moony-Stary May 04 '23 at 11:08
There is none. It also doesn't make sense because the result should be given back to the route that pushed the route.
If you really have to do this, use multiple pop
calls:
// 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

- 28,207
- 10
- 81
- 66
-
this will get displayed in view while the `popUntil` will not. So it would be better if we able to popUntil and pass the data also. – Dinesh Balasubramanian Aug 29 '18 at 16:23
-
Yeah, there are other ways. I would like to know what OP is trying to do. It's probably a conceptual issue how he is using the `Navigator`. – boformer Aug 30 '18 at 10:03
-
1This appears to be the most clean approach even though it causes tight coupling. I don't think the coupling is an issue though as it's two layers deep and probably won't occur often in most apps. If you have to go three layers back, I'd question this approach as too unwieldy. – AutoM8R Sep 09 '21 at 19:01
plus @hunghd answer's make sure that the screen settings
that you will popback must not have a const
key. otherwise you will be faced following error:
Unhandled Exception: Unsupported operation: Cannot modify unmodifiable map
I just removed the const
key from settings on following code where the user will be popped back.
case HomeViewRoute:
return MaterialPageRoute(
builder: (context) => HomePage(),
settings: RouteSettings(name: HomeViewRoute, arguments: {}),
);

- 3,387
- 3
- 34
- 51
Here is the solution for single shot argument reading. As the solution proposed by @hunghd does not take this into account. The trick here is to consume arguments once, not many times as it might lead to wrong behaviours.
If we want to "go back with single result" we should do the following thing. We can draft the following engine in three steps:
First step - setup in the 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,
);
Second step - Performing navigation:
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;
}
}
*Third step - passing arguments back:
// 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;
});
}
}
Of course this solution passes redundant bool
. And this raises questions whether a Map
is needed. For single-shot cases I believe that this can be improved by using Set<String>
instead of Map
.

- 1,449
- 1
- 20
- 19
For workaround solution until the Flutter team provides it, you can do it using shared_preferences.
- Add class with function to get and save value to shared_preferences.
static const isBackFromPopUntil = "isBackFromPopUntil";
Future<void> saveIsBackFromPopUntil(bool isBack) async =>
await preferences.setBool(isBackFromPopUntil, isBack);
bool getIsBackFromPopUntil() =>
preferences.getBool(isBackFromPopUntil) ?? false;
- For example you need to check "someting" when modal bottom sheet is closed from
popUntil
, otherwise not.
void _openBottomSheet(BuildContext context) async {
await showCupertinoModalBottomSheet(
context: context,
isDismissible: true,
...
);
final isBack = _prefHelper.getIsBackFromPopUntil();
if (isBack) {
// do stuff
await _prefHelper.saveIsBackFromPopUntil(false);
}
}
- You back from the screen but using
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));
}

- 1,915
- 6
- 27
- 54