Add navigatorKey to CupertinoTabView (#25183)

diff --git a/packages/flutter/lib/src/cupertino/tab_view.dart b/packages/flutter/lib/src/cupertino/tab_view.dart
index 08bb615..03746ff 100644
--- a/packages/flutter/lib/src/cupertino/tab_view.dart
+++ b/packages/flutter/lib/src/cupertino/tab_view.dart
@@ -43,6 +43,7 @@
   const CupertinoTabView({
     Key key,
     this.builder,
+    this.navigatorKey,
     this.defaultTitle,
     this.routes,
     this.onGenerateRoute,
@@ -58,6 +59,19 @@
   /// as [builder] takes its place.
   final WidgetBuilder builder;
 
+  /// A key to use when building this widget's [Navigator].
+  ///
+  /// If a [navigatorKey] is specified, the [Navigator] can be directly
+  /// manipulated without first obtaining it from a [BuildContext] via
+  /// [Navigator.of]: from the [navigatorKey], use the [GlobalKey.currentState]
+  /// getter.
+  ///
+  /// If this is changed, a new [Navigator] will be created, losing all the
+  /// tab's state in the process; in that case, the [navigatorObservers]
+  /// must also be changed, since the previous observers will be attached to the
+  /// previous navigator.
+  final GlobalKey<NavigatorState> navigatorKey;
+
   /// The title of the default route.
   final String defaultTitle;
 
@@ -120,9 +134,12 @@
   }
 
   @override
-   void didUpdateWidget(CupertinoTabView oldWidget) {
-     super.didUpdateWidget(oldWidget);
-     _updateObservers();
+  void didUpdateWidget(CupertinoTabView oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.navigatorKey != oldWidget.navigatorKey
+        || widget.navigatorObservers != oldWidget.navigatorObservers) {
+      _updateObservers();
+    }
   }
 
   void _updateObservers() {
@@ -134,6 +151,7 @@
   @override
   Widget build(BuildContext context) {
     return Navigator(
+      key: widget.navigatorKey,
       onGenerateRoute: _onGenerateRoute,
       onUnknownRoute: _onUnknownRoute,
       observers: _navigatorObservers,
diff --git a/packages/flutter/test/cupertino/tab_test.dart b/packages/flutter/test/cupertino/tab_test.dart
index 08a873e..ce17615 100644
--- a/packages/flutter/test/cupertino/tab_test.dart
+++ b/packages/flutter/test/cupertino/tab_test.dart
@@ -96,4 +96,75 @@
     expect(tester.takeException(), isFlutterError);
     expect(unknownForRouteCalled, '/');
   });
+
+  testWidgets('Can use navigatorKey to navigate', (WidgetTester tester) async {
+    final GlobalKey<NavigatorState> key = GlobalKey();
+    await tester.pumpWidget(
+      CupertinoApp(
+        home: CupertinoTabView(
+          navigatorKey: key,
+          builder: (BuildContext context) => const Text('first route'),
+          routes: <String, WidgetBuilder>{
+            '/2': (BuildContext context) => const Text('second route'),
+          },
+        ),
+      ),
+    );
+
+    key.currentState.pushNamed('/2');
+
+    await tester.pump();
+    await tester.pump(const Duration(milliseconds: 300));
+    expect(find.text('second route'), findsOneWidget);
+  });
+
+  testWidgets('Changing the key resets the navigator', (WidgetTester tester) async {
+    final GlobalKey<NavigatorState> key = GlobalKey();
+    await tester.pumpWidget(
+      CupertinoApp(
+        home: CupertinoTabView(
+          builder: (BuildContext context) {
+            return CupertinoButton(
+              child: const Text('go to second page'),
+              onPressed: () {
+                Navigator.of(context).pushNamed('/2');
+              },
+            );
+          },
+          routes: <String, WidgetBuilder>{
+            '/2': (BuildContext context) => const Text('second route'),
+          },
+        ),
+      ),
+    );
+
+    expect(find.text('go to second page'), findsOneWidget);
+    await tester.tap(find.text('go to second page'));
+    await tester.pump();
+    await tester.pump(const Duration(milliseconds: 300));
+    expect(find.text('second route'), findsOneWidget);
+
+    await tester.pumpWidget(
+      CupertinoApp(
+        home: CupertinoTabView(
+          key: key,
+          builder: (BuildContext context) {
+            return CupertinoButton(
+              child: const Text('go to second page'),
+              onPressed: () {
+                Navigator.of(context).pushNamed('/2');
+              },
+            );
+          },
+          routes: <String, WidgetBuilder>{
+            '/2': (BuildContext context) => const Text('second route'),
+          },
+        ),
+      ),
+    );
+
+    // The stack is gone and we're back to a re-built page 1.
+    expect(find.text('go to second page'), findsOneWidget);
+    expect(find.text('second route'), findsNothing);
+  });
 }