diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart index 1a39e20e8ef..281039b3e10 100644 --- a/packages/flutter/lib/src/widgets/navigator.dart +++ b/packages/flutter/lib/src/widgets/navigator.dart @@ -3642,8 +3642,12 @@ class NavigatorState extends State with TickerProviderStateMixin { /// ``` /// {@end-tool} void popUntil(RoutePredicate predicate) { - while (!predicate(_history.lastWhere(_RouteEntry.isPresentPredicate).route)) { + _RouteEntry candidate = _history.lastWhere(_RouteEntry.isPresentPredicate, orElse: () => null); + while(candidate != null) { + if (predicate(candidate.route)) + return; pop(); + candidate = _history.lastWhere(_RouteEntry.isPresentPredicate, orElse: () => null); } } diff --git a/packages/flutter/test/widgets/navigator_test.dart b/packages/flutter/test/widgets/navigator_test.dart index 9a34e01b04b..ea723430ca9 100644 --- a/packages/flutter/test/widgets/navigator_test.dart +++ b/packages/flutter/test/widgets/navigator_test.dart @@ -696,6 +696,33 @@ void main() { expect(find.text('C'), isOnstage); }); + testWidgets('Able to pop all routes', (WidgetTester tester) async { + final Map routes = { + '/' : (BuildContext context) => const OnTapPage( + id: '/', + ), + '/A': (BuildContext context) => const OnTapPage( + id: 'A', + ), + '/A/B': (BuildContext context) => OnTapPage( + id: 'B', + onTap: (){ + // Pops all routes with bad predicate. + Navigator.of(context).popUntil((Route route) => false); + }, + ), + }; + await tester.pumpWidget( + MaterialApp( + routes: routes, + initialRoute: '/A/B', + ) + ); + await tester.tap(find.text('B')); + await tester.pumpAndSettle(); + expect(tester.takeException(), isNull); + }); + testWidgets('pushAndRemoveUntil triggers secondaryAnimation', (WidgetTester tester) async { final Map routes = { '/' : (BuildContext context) => OnTapPage(