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);
+ });
}