Skip to content

Conversation

@alex-medinsh
Copy link
Contributor

@alex-medinsh alex-medinsh commented May 23, 2025

This PR adds a new Navigator.popUntilWithResult method that allows to pass a value to the last pop call.

Fixes #30112

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@github-actions github-actions bot added framework flutter/packages/flutter repository. See also f: labels. f: routes Navigator, Router, and related APIs. labels May 23, 2025
@alex-medinsh alex-medinsh force-pushed the popuntil-return-data branch from 5d2a3c3 to 2577501 Compare May 23, 2025 13:48
@alex-medinsh alex-medinsh marked this pull request as ready for review May 23, 2025 15:27
@alex-medinsh
Copy link
Contributor Author

I don't like that predicate is getting called twice on each route, but that can be fixed. What I am not sure about is whether it is safe to assume that the predicate result won't change between checking it as a next route vs as a current route?

@chunhtai chunhtai self-requested a review May 23, 2025 18:37
@alex-medinsh alex-medinsh force-pushed the popuntil-return-data branch from 893e698 to bb4e943 Compare May 28, 2025 19:57
@alex-medinsh
Copy link
Contributor Author

@chunhtai I have made the changes, PTAL.
It still has a problem of testing the route as next, but I am not sure if there even is a different way in this case.
This way does give more control to the developer, but I feel like the majority of cases (judging from the issue) is still going to be

Navigator.popUntil(
   context,
  (Route<dynamic> route) => route.isFirst,
  (Route<dynamic> route) => route.isFirst ? true : null,
);

to just return a value to the first route.

@chunhtai
Copy link
Contributor

I think the current approach should be fine, if they really want a special case, they can write a helper function. I want to avoid having conflict that people want a slightly different variant of your use case and we would need to end up adding a different popUntil API.

@alex-medinsh
Copy link
Contributor Author

I think you are right. I am worried that there can be a predicate that when tested as the next route will return a different result to when it was tested as the current route. Do you think this can be an issue?

Copy link
Contributor

@chunhtai chunhtai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah I didn't realized the resultPredicate is call on the next route to pop the current route. This doesn't look right. I imagine the correct way would be calling the resultpredicate on the current route. That will of course need some refactoring to correctly capture the last popped route, if they gave the last popped route an appropriate name, it should be fine I think

@alex-medinsh alex-medinsh force-pushed the popuntil-return-data branch from bb4e943 to 9dcf0ff Compare June 5, 2025 09:34
@alex-medinsh
Copy link
Contributor Author

@chunhtai I have reverted the changes, please take a look.

Regarding your last question:

I believe that any predicate that for some reason checks route.isCurrent (since it will now be false for all but the first checked route) or any other predicate that only becomes true when this route is the top one will break because of these changes.

Maybe I am overthinking this. Do you think this is an issue?

@alex-medinsh alex-medinsh requested a review from chunhtai June 5, 2025 10:19
@alex-medinsh
Copy link
Contributor Author

I think I found an example of such a predicate:

testWidgets('Route localHistory - popUntil', (WidgetTester tester) async {

To make the test pass, I had to leave this check:

if (predicate(candidate.route)) {
return;
}

This works, but it now has an issue with checking each route twice. Once as the next route, and once as the current route. This happens because checking willHandlePopInternally before the pop returns false, so we pop, but then if we don't check the predicate the second time, we will pop an extra time.

@chunhtai
Copy link
Contributor

chunhtai commented Jun 5, 2025

I see now, I will take another look

@alex-medinsh alex-medinsh force-pushed the popuntil-return-data branch from c2449be to dac9f04 Compare June 11, 2025 06:35
@alex-medinsh alex-medinsh requested a review from chunhtai June 11, 2025 09:06
Copy link
Contributor

@chunhtai chunhtai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@chunhtai chunhtai requested a review from justinmc June 12, 2025 22:43
Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the comment about the bug in the test, otherwise looks good. Thanks for wiring this up!

@alex-medinsh alex-medinsh force-pushed the popuntil-return-data branch from eb234b2 to 007972f Compare June 14, 2025 09:46
Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also seeing an analyzer failure in the Google tests that probably needs to be addressed first. Let me know what you think.

In analysis_options.dart:

analyzer:
  language:
    strict-casts: true
    strict-inference: true
    strict-raw-types: true

Then in main.dart:

Repro app code
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

void Function(BuildContext, RoutePredicate)? debugPopUntil;

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            TextButton(
              onPressed: () {
                final popUntil = kDebugMode ? debugPopUntil! : Navigator.popUntil;
                popUntil(
                  context,
                  (route) =>
                      ModalRoute.withName('myroute')(route) || route.isFirst,
                );
              },
              child: const Text('press me'),
            )
          ],
        ),
      ),
    );
  }
}
$ dart analyze                                                                                                                                                                                                    3 ✘  13:12:00 
Analyzing sandbox...                   1.2s

  error • lib/main.dart:46:54 • The argument type 'dynamic' can't be assigned to the parameter type 'Route<dynamic>'.  • argument_type_not_assignable
  error • lib/main.dart:46:64 • The operands of the operator '||' must be assignable to 'bool'. • non_bool_operand
warning • lib/main.dart:45:20 • The type of route can't be inferred; a type must be explicitly provided. Try specifying the type of the parameter. • inference_failure_on_untyped_parameter

3 issues found.

@alex-medinsh alex-medinsh force-pushed the popuntil-return-data branch from 58189f9 to e2d388f Compare June 27, 2025 09:36
@alex-medinsh
Copy link
Contributor Author

@justinmc The issue was that the signature of popUntil changed, but the signature of debugPopUntil was still the old one, so Dart had troubles with the types. Updating the signature to

void Function<T extends Object?>(BuildContext, RoutePredicate, [T?])?
debugPopUntil;

fixes the issue. Should something be done on my side or should this be fixed on the Google's side?

@alex-medinsh alex-medinsh force-pushed the popuntil-return-data branch from e2d388f to 0f059e9 Compare July 8, 2025 08:14
@Piinks Piinks requested a review from justinmc July 30, 2025 18:26
@alex-medinsh
Copy link
Contributor Author

Hi @justinmc, I think we can merge this?

Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, will autosubmit.

/// If `result` is specified, then the last popped route will be closed with
/// `result` as its return value.
///
/// The `T` type argument is the type of the return value of the last popped route.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized I never responded to this, apologies! I think this was just done in our overall effort to be strict about types. It's probably a bit of a stretch in this case, but it probably could statically catch some accidental type change inside the implementation of pop that wouldn't be caught until runtime if it used Object.

@justinmc justinmc added the autosubmit Merge PR when tree becomes green via auto submit App label Oct 29, 2025
@auto-submit auto-submit bot added this pull request to the merge queue Oct 29, 2025
Merged via the queue into flutter:master with commit 37125ff Oct 29, 2025
78 checks passed
@flutter-dashboard flutter-dashboard bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Oct 29, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Oct 29, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Oct 29, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Oct 29, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Oct 29, 2025
auto-submit bot pushed a commit to flutter/packages that referenced this pull request Oct 29, 2025
flutter/flutter@7cf0dc1...df72035

2025-10-29 [email protected] Roll Packages from c8ba0cc to 41c6b3d (3 revisions) (flutter/flutter#177725)
2025-10-29 [email protected] [Android] Remove unecessary `spy` in `FlutterActivityAndFragmentDelegateTest` (flutter/flutter#177120)
2025-10-29 [email protected] Add discussion of federated plugin documentation (flutter/flutter#177659)
2025-10-29 [email protected] [web] Deprecate --pwa-strategy (flutter/flutter#177613)
2025-10-29 [email protected] Roll Skia from 53b8b802bbc4 to 0a0c9f8c704f (1 revision) (flutter/flutter#177701)
2025-10-29 [email protected] Add `Navigator.popUntilWithResult` (flutter/flutter#169341)
2025-10-29 [email protected] Replace deprecated `withOpacity` with  `withValues` in `text_style.dart` (flutter/flutter#177537)
2025-10-29 [email protected] Replace deprecated withOpacity in `radio.1.dart` example (flutter/flutter#177606)
2025-10-29 [email protected] Roll Skia from 4408d8ea88b0 to 53b8b802bbc4 (2 revisions) (flutter/flutter#177698)
2025-10-29 [email protected] Remove generated file from template manifest (flutter/flutter#177034)
2025-10-29 [email protected] Roll Skia from e582a5594b6f to 4408d8ea88b0 (5 revisions) (flutter/flutter#177691)
2025-10-28 [email protected] Roll Fuchsia Linux SDK from ir6J2isKAYa1jNLyJ... to 3EF6k6lqXPWDwrdyj... (flutter/flutter#177682)
2025-10-28 [email protected] [Gradle 9] Fix Engine Deps (flutter/flutter#177623)
2025-10-28 [email protected] Replace deprecated `withOpacity` in `focus_scope.0.dart‎` example  (flutter/flutter#177542)
2025-10-28 [email protected] Fix EditableText _justResumed is not accurate (flutter/flutter#177658)
2025-10-28 [email protected] Replace deprecated `withOpacity` in `interactive_viewer.builder.0.dart` (flutter/flutter#177541)
2025-10-28 [email protected] Fix TextButton.icon breaks focus traversal and ink effect when toggling icon (flutter/flutter#176579)
2025-10-28 [email protected] [Android 16] Update Engine `ci.yaml` to test against Java 21 (flutter/flutter#177677)
2025-10-28 [email protected] Replace deprecated `withOpacity` in `interactive_viewer.constrained.0.dart` (flutter/flutter#177540)
2025-10-28 [email protected] Replace opacity from random color in navigation bar test (flutter/flutter#177490)
2025-10-28 [email protected] Roll Skia from e4d3d8f31aef to e582a5594b6f (6 revisions) (flutter/flutter#177679)
2025-10-28 [email protected] Workaround for lag when dragging window titlebar on Windows (flutter/flutter#177597)
2025-10-28 [email protected] Roll Packages from bbf96a0 to c8ba0cc (2 revisions) (flutter/flutter#177672)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC [email protected],[email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
walley892 pushed a commit to walley892/flutter that referenced this pull request Oct 30, 2025
<!--
Thanks for filing a pull request!
Reviewers are typically assigned within a week of filing a request.
To learn more about code review, see our documentation on Tree Hygiene:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
-->

This PR adds a new `Navigator.popUntilWithResult` method that allows to
pass a value to the last `pop` call.

Fixes flutter#30112

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
reidbaker pushed a commit to AbdeMohlbi/flutter that referenced this pull request Dec 10, 2025
<!--
Thanks for filing a pull request!
Reviewers are typically assigned within a week of filing a request.
To learn more about code review, see our documentation on Tree Hygiene:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
-->

This PR adds a new `Navigator.popUntilWithResult` method that allows to
pass a value to the last `pop` call.

Fixes flutter#30112

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

f: routes Navigator, Router, and related APIs. framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Return data with popUntil

4 participants