diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart index 75e25670c1..c3f33f0457 100644 --- a/packages/flutter/lib/src/widgets/navigator.dart +++ b/packages/flutter/lib/src/widgets/navigator.dart @@ -5023,11 +5023,15 @@ class NavigatorState extends State with TickerProviderStateMixin, Res bool? wasDebugLocked; assert(() { wasDebugLocked = _debugLocked; _debugLocked = true; return true; }()); assert(_history.where(_RouteEntry.isRoutePredicate(route)).length == 1); - final _RouteEntry entry = _history.firstWhere(_RouteEntry.isRoutePredicate(route)); - // For page-based route, the didPop can be called on any life cycle above - // pop. - assert(entry.currentState == _RouteLifecycle.popping || - (entry.hasPage && entry.currentState.index < _RouteLifecycle.pop.index)); + final int index = _history.indexWhere(_RouteEntry.isRoutePredicate(route)); + final _RouteEntry entry = _history[index]; + // For page-based route with zero transition, the finalizeRoute can be + // called on any life cycle above pop. + if (entry.hasPage && entry.currentState.index < _RouteLifecycle.pop.index) { + _observedRouteDeletions.add(_NavigatorPopObservation(route, _getRouteBefore(index - 1, _RouteEntry.willBePresentPredicate)?.route)); + } else { + assert(entry.currentState == _RouteLifecycle.popping); + } entry.finalize(); // finalizeRoute can be called during _flushHistoryUpdates if a pop // finishes synchronously. diff --git a/packages/flutter/test/widgets/navigator_test.dart b/packages/flutter/test/widgets/navigator_test.dart index 512d5b6c6d..d7bb1829ad 100644 --- a/packages/flutter/test/widgets/navigator_test.dart +++ b/packages/flutter/test/widgets/navigator_test.dart @@ -183,6 +183,47 @@ void main() { expect('$exception', startsWith('Navigator operation requested with a context')); }); + testWidgets('Zero transition page-based route correctly notifies observers when it is popped', (WidgetTester tester) async { + final List> pages = >[ + const ZeroTransitionPage(name: 'Page 1'), + const ZeroTransitionPage(name: 'Page 2'), + ]; + final List observations = []; + + final TestObserver observer = TestObserver() + ..onPopped = (Route? route, Route? previousRoute) { + observations.add( + NavigatorObservation( + current: route?.settings.name, + previous: previousRoute?.settings.name, + operation: 'pop', + ), + ); + }; + final GlobalKey navigator = GlobalKey(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: Navigator( + key: navigator, + pages: pages, + observers: [observer], + onPopPage: (Route route, dynamic result) { + pages.removeLast(); + return route.didPop(result); + }, + ), + ), + ); + + navigator.currentState!.pop(); + await tester.pump(); + + expect(observations.length, 1); + expect(observations[0].current, 'Page 2'); + expect(observations[0].previous, 'Page 1'); + }); + testWidgets('Navigator.of rootNavigator finds root Navigator', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( home: Material( @@ -3941,6 +3982,22 @@ class AlwaysRemoveTransitionDelegate extends TransitionDelegate { } } +class ZeroTransitionPage extends Page { + const ZeroTransitionPage({ + LocalKey? key, + Object? arguments, + required String name, + }) : super(key: key, name: name, arguments: arguments); + + @override + Route createRoute(BuildContext context) { + return NoAnimationPageRoute( + settings: this, + pageBuilder: (BuildContext context) => Text(name!), + ); + } +} + class TestPage extends Page { const TestPage({ LocalKey? key, @@ -3958,15 +4015,17 @@ class TestPage extends Page { } class NoAnimationPageRoute extends PageRouteBuilder { - NoAnimationPageRoute({required WidgetBuilder pageBuilder}) - : super(pageBuilder: (BuildContext context, __, ___) { - return pageBuilder(context); - }); - - @override - AnimationController createAnimationController() { - return super.createAnimationController()..value = 1.0; - } + NoAnimationPageRoute({ + RouteSettings? settings, + required WidgetBuilder pageBuilder + }) : super( + settings: settings, + transitionDuration: Duration.zero, + reverseTransitionDuration: Duration.zero, + pageBuilder: (BuildContext context, __, ___) { + return pageBuilder(context); + } + ); } class StatefulTestWidget extends StatefulWidget {