diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart index 1e0ddb74e9dbe..168099677e604 100644 --- a/packages/flutter/lib/src/widgets/navigator.dart +++ b/packages/flutter/lib/src/widgets/navigator.dart @@ -213,7 +213,11 @@ abstract class Route { // focused child can only be attached to navigator after initState which // will be guarded by the asynchronous gap. TickerFuture.complete().then((void _) { - navigator.focusScopeNode.requestFocus(); + // The route can be disposed before the ticker future completes. This can + // happen when the navigator is under a TabView that does a warping. The + // navigator will be null in this case, and we have to do a null-safe + // operation. + navigator?.focusScopeNode?.requestFocus(); }); } diff --git a/packages/flutter/test/widgets/navigator_test.dart b/packages/flutter/test/widgets/navigator_test.dart index 2faa0cb1e6d1b..1ab66a8baa014 100644 --- a/packages/flutter/test/widgets/navigator_test.dart +++ b/packages/flutter/test/widgets/navigator_test.dart @@ -489,6 +489,38 @@ void main() { expect(observations[2].previous, '/A'); }); + testWidgets('Route didAdd and dispose in same frame work', (WidgetTester tester) async { + // Regression Test for https://github.com/flutter/flutter/issues/61346. + Widget buildNavigator() { + return Navigator( + pages: >[ + MaterialPage( + builder: (BuildContext context) => const Placeholder(), + ) + ], + onPopPage: (Route route, dynamic result) => false, + ); + } + final TabController controller = TabController(length: 3, vsync: tester); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: TabBarView( + controller: controller, + children: [ + buildNavigator(), + buildNavigator(), + buildNavigator(), + ], + ) + ), + ); + + // This test should finish without crashing. + controller.index = 2; + await tester.pumpAndSettle(); + }); + testWidgets('replaceNamed replaces', (WidgetTester tester) async { final Map routes = { '/' : (BuildContext context) => OnTapPage(id: '/', onTap: () { Navigator.pushReplacementNamed(context, '/A'); }),