Make CupertinoTabView restorable (#67169)

diff --git a/packages/flutter/lib/src/cupertino/tab_view.dart b/packages/flutter/lib/src/cupertino/tab_view.dart
index 7c47829..df16682 100644
--- a/packages/flutter/lib/src/cupertino/tab_view.dart
+++ b/packages/flutter/lib/src/cupertino/tab_view.dart
@@ -9,9 +9,9 @@
 
 /// A single tab view with its own [Navigator] state and history.
 ///
-/// A typical tab view used as the content of each tab in a [CupertinoTabScaffold]
-/// where multiple tabs with parallel navigation states and history can
-/// co-exist.
+/// A typical tab view is used as the content of each tab in a
+/// [CupertinoTabScaffold] where multiple tabs with parallel navigation states
+/// and history can co-exist.
 ///
 /// [CupertinoTabView] configures the top-level [Navigator] to search for routes
 /// in the following order:
@@ -49,6 +49,7 @@
     this.onGenerateRoute,
     this.onUnknownRoute,
     this.navigatorObservers = const <NavigatorObserver>[],
+    this.restorationScopeId,
   }) : assert(navigatorObservers != null),
        super(key: key);
 
@@ -125,6 +126,12 @@
   /// This list of observers is not shared with ancestor or descendant [Navigator]s.
   final List<NavigatorObserver> navigatorObservers;
 
+  /// Restoration ID to save and restore the state of the [Navigator] built by
+  /// this [CupertinoTabView].
+  ///
+  /// {@macro flutter.widgets.navigator.restorationScopeId}
+  final String? restorationScopeId;
+
   @override
   _CupertinoTabViewState createState() {
     return _CupertinoTabViewState();
@@ -164,6 +171,7 @@
       onGenerateRoute: _onGenerateRoute,
       onUnknownRoute: _onUnknownRoute,
       observers: _navigatorObservers,
+      restorationScopeId: widget.restorationScopeId,
     );
   }
 
diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart
index f535888..512336f 100644
--- a/packages/flutter/lib/src/widgets/navigator.dart
+++ b/packages/flutter/lib/src/widgets/navigator.dart
@@ -1528,6 +1528,7 @@
   /// Restoration ID to save and restore the state of the navigator, including
   /// its history.
   ///
+  /// {@template flutter.widgets.navigator.restorationScopeId}
   /// If a restoration ID is provided, the navigator will persist its internal
   /// state (including the route history as well as the restorable state of the
   /// routes) and restore it during state restoration.
@@ -1538,11 +1539,20 @@
   ///
   /// The state is persisted in a [RestorationBucket] claimed from
   /// the surrounding [RestorationScope] using the provided restoration ID.
+  /// Within that bucket, the [Navigator] also creates a new [RestorationScope]
+  /// for its children (the [Route]s).
   ///
   /// See also:
   ///
   ///  * [RestorationManager], which explains how state restoration works in
   ///    Flutter.
+  ///  * [RestorationMixin], which contains a runnable code sample showcasing
+  ///    state restoration in Flutter.
+  ///  * [Navigator], which explains under the heading "state restoration"
+  ///    how and under what conditions the navigator restores its state.
+  ///  * [Navigator.restorablePush], which includes an example showcasing how
+  ///    to push a restorable route unto the navigator.
+  /// {@endtemplate}
   final String? restorationScopeId;
 
   /// The name for the default route of the application.
diff --git a/packages/flutter/test/cupertino/tab_test.dart b/packages/flutter/test/cupertino/tab_test.dart
index 64854f6..1686890 100644
--- a/packages/flutter/test/cupertino/tab_test.dart
+++ b/packages/flutter/test/cupertino/tab_test.dart
@@ -237,4 +237,55 @@
       '   callback returned null. Such callbacks must never return null.\n'
     ));
   });
+
+  testWidgets('Navigator of CupertinoTabView restores state', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      CupertinoApp(
+        restorationScopeId: 'app',
+        home: CupertinoTabView(
+          restorationScopeId: 'tab',
+          builder: (BuildContext context) => CupertinoButton(
+            child: const Text('home'),
+            onPressed: () {
+              Navigator.of(context).restorablePushNamed('/2');
+            },
+          ),
+          routes: <String, WidgetBuilder>{
+            '/2' : (BuildContext context) => const Text('second route'),
+          }
+        ),
+      ),
+    );
+
+    expect(find.text('home'), findsOneWidget);
+    await tester.tap(find.text('home'));
+    await tester.pumpAndSettle();
+
+    expect(find.text('home'), findsNothing);
+    expect(find.text('second route'), findsOneWidget);
+
+    final TestRestorationData data = await tester.getRestorationData();
+
+    await tester.restartAndRestore();
+
+    expect(find.text('home'), findsNothing);
+    expect(find.text('second route'), findsOneWidget);
+
+    Navigator.of(tester.element(find.text('second route'))).pop();
+    await tester.pumpAndSettle();
+
+    expect(find.text('home'), findsOneWidget);
+    expect(find.text('second route'), findsNothing);
+
+    await tester.restoreFrom(data);
+
+    expect(find.text('home'), findsNothing);
+    expect(find.text('second route'), findsOneWidget);
+
+    Navigator.of(tester.element(find.text('second route'))).pop();
+    await tester.pumpAndSettle();
+
+    expect(find.text('home'), findsOneWidget);
+    expect(find.text('second route'), findsNothing);
+  });
 }