How to return data when using popuntil() ?

Raveena Naik
4 min readJan 27, 2024

--

Flutter’s navigation system provides a powerful way to manage the flow of screens within your app. In Flutter these screens or pages are called routes and they’re managed by a Navigator widget. The Navigator is a widget that manages a stack of pages, representing the app’s navigation history. It provides methods to push new screens onto the stack, pop screens off the stack, and replace existing screens.

Here in this article we are going to discuss the Navigator.popUntil() method. As we all know we can easily return data with the Navigator.pop() method. But have you ever felt a need to use popUntil() and return some data ?

Suppose for an example, we have a form with multiple parts, shown in different routes or screens. On the last screen, after submitting the form, we have to navigate back to the first screen. Well this can be easily achieved using Navigator.popUntil(). But what if we want to show a snackbar or some message after submitting the form ? The popUntil method currently doesn’t allow you to send return data. If you take a look at the documentation, popUntil is a loop of pop without parameters for return data.

Here is a workaround for the same.

The basic idea is to use named routes and set the arguments for the routes. So that we can pass or return data as the arguments.

STEP 1

In the code below, I have simply created a home page and 4 screens named Screen A, B, C and D.

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_popuntil_with_data/screens/home.dart';
import 'package:flutter_popuntil_with_data/screens/screen_a.dart';
import 'package:flutter_popuntil_with_data/screens/screen_b.dart';
import 'package:flutter_popuntil_with_data/screens/screen_c.dart';
import 'package:flutter_popuntil_with_data/screens/screen_d.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHome(title: 'Home Page'),
routes: {
"/goto_screen_a": (BuildContext context) => const ScreenA(),
"/goto_screen_b": (BuildContext context) => const ScreenB(),
"/goto_screen_c": (BuildContext context) => const ScreenC(),
"/goto_screen_d": (BuildContext context) => const ScreenD(),
});
}
}

home.dart

import 'package:flutter/material.dart';
class MyHome extends StatefulWidget {
const MyHome({super.key, required this.title});
final String title;
@override
State<MyHome> createState() => _MyHomeState();
}
class _MyHomeState extends State<MyHome> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.primary,
title: Text(
widget.title,
style: const TextStyle(color: Colors.white),
),
),
bottomNavigationBar: SafeArea(
child: Container(
padding: const EdgeInsets.all(20.0),
child: TextButton(
onPressed: () {

Navigator.pushNamed(context, "/goto_screen_a");
},
style: TextButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'Next',
style: TextStyle(
color: Colors.white,
),
),
)),
)),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'My home page',
),
],
),
),
);
}
}

screen_a.dart

import 'package:flutter/material.dart';
import 'package:flutter_popuntil_with_data/screens/screen_b.dart';
class ScreenA extends StatefulWidget {
const ScreenA({
super.key,
});
@override
State<ScreenA> createState() => _ScreenAState();
}
class _ScreenAState extends State<ScreenA> {
var textContent = 'Screen A';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.primary,
title: const Text(
'Screen A',
style: TextStyle(color: Colors.white),
),
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.pop(context),
),
),
bottomNavigationBar: SafeArea(
child: Container(
padding: const EdgeInsets.all(20.0),
child: TextButton(
onPressed: () {
Navigator.pushNamed(context, "/goto_screen_b");
},
style: TextButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'Next',
style: TextStyle(
color: Colors.white,
),
),
)),
)),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Screen A',
),
],
),
),
);
}
}

In main.dart, I have created named routes for all the 4 screens and added some basic layout for all the screens.
Here, I have routes from Screen A to B, B to C, C to D; and on Screen D I have given a submit button which on click should navigate us back to Screen A along with data.

STEP 2

Now, since we have to route back to Screen A we have to initialize the route to screen a with some data. So, in home.dart, Let’s initiate the route to Screen A by passing a blank argument like so:

Navigator.pushNamed(context, "/goto_screen_a", arguments: {
'result': '',
});

STEP 3

Now, In screen_d.dart, we are going to use the popUntil() method. I have added this piece of code for the submit button.

Navigator.popUntil(context, ((route) {
if (route.settings.name == '/goto_screen_a') {
(route.settings.arguments as Map)['result'] =
'Your request has been submitted successfully..!!';
return true;
} else {
return false;
}
}));

Here, we are just checking the condition for the route, whether or not the route name is ‘goto_screen_a’. If the condition is satisfied then, we are going to pass an object or data to the parameter ‘arguments’ in the route settings. Here I have passed a string specifying that the form is successfully submitted.

STEP 4

Finally, if we check the output the routes are working as expected. The only part remaining is to show a snackbar with the message. Heading back to screen_a.dart, I simply have to fetch the arguments, store the returned string and show it as a message.

Navigator.pushNamed(context, "/goto_screen_b").then((value) {
final arguments =
ModalRoute.of(context)?.settings.arguments as Map;
final result = arguments['result'];
setState(() {
if (result != null && result != '') {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor:
Theme.of(context).colorScheme.secondary,
duration: const Duration(seconds: 3),
behavior: SnackBarBehavior.floating,
content: Text(
result,
style: const TextStyle(color: Colors.white),
),
),
);
}
});
});

Now when you click on the submit button on the Screen D, you will be navigated back to Screen A with a snackbar showing success message like so:

I hope you find this article helpful. Happy Coding..!!

You can find this complete code on:
https://github.com/naikravee/flutter_popuntil

--

--