Unverified Commit fc85492d authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Make Navigator restorable (inkl. WidgetsApp, MaterialApp, CupertinoApp) (#65658)

parent 5d6321b5
......@@ -94,6 +94,7 @@ class CupertinoApp extends StatefulWidget {
this.debugShowCheckedModeBanner = true,
this.shortcuts,
this.actions,
this.restorationScopeId,
}) : assert(routes != null),
assert(navigatorObservers != null),
assert(title != null),
......@@ -132,6 +133,7 @@ class CupertinoApp extends StatefulWidget {
this.debugShowCheckedModeBanner = true,
this.shortcuts,
this.actions,
this.restorationScopeId,
}) : assert(title != null),
assert(showPerformanceOverlay != null),
assert(checkerboardRasterCacheImages != null),
......@@ -315,6 +317,9 @@ class CupertinoApp extends StatefulWidget {
/// {@macro flutter.widgets.widgetsApp.actions.seeAlso}
final Map<Type, Action<Intent>>? actions;
/// {@macro flutter.widgets.widgetsApp.restorationScopeId}
final String? restorationScopeId;
@override
_CupertinoAppState createState() => _CupertinoAppState();
......@@ -400,6 +405,7 @@ class _CupertinoAppState extends State<CupertinoApp> {
inspectorSelectButtonBuilder: _inspectorSelectButtonBuilder,
shortcuts: widget.shortcuts,
actions: widget.actions,
restorationScopeId: widget.restorationScopeId,
);
}
return WidgetsApp(
......@@ -433,6 +439,7 @@ class _CupertinoAppState extends State<CupertinoApp> {
inspectorSelectButtonBuilder: _inspectorSelectButtonBuilder,
shortcuts: widget.shortcuts,
actions: widget.actions,
restorationScopeId: widget.restorationScopeId,
);
}
......
......@@ -197,6 +197,7 @@ class MaterialApp extends StatefulWidget {
this.debugShowCheckedModeBanner = true,
this.shortcuts,
this.actions,
this.restorationScopeId,
}) : assert(routes != null),
assert(navigatorObservers != null),
assert(title != null),
......@@ -241,6 +242,7 @@ class MaterialApp extends StatefulWidget {
this.debugShowCheckedModeBanner = true,
this.shortcuts,
this.actions,
this.restorationScopeId,
}) : assert(routeInformationParser != null),
assert(routerDelegate != null),
assert(title != null),
......@@ -620,6 +622,9 @@ class MaterialApp extends StatefulWidget {
/// {@macro flutter.widgets.widgetsApp.actions.seeAlso}
final Map<Type, Action<Intent>> actions;
/// {@macro flutter.widgets.widgetsApp.restorationScopeId}
final String restorationScopeId;
/// Turns on a [GridPaper] overlay that paints a baseline grid
/// Material apps.
///
......@@ -780,6 +785,7 @@ class _MaterialAppState extends State<MaterialApp> {
inspectorSelectButtonBuilder: _inspectorSelectButtonBuilder,
shortcuts: widget.shortcuts,
actions: widget.actions,
restorationScopeId: widget.restorationScopeId,
);
}
......@@ -814,6 +820,7 @@ class _MaterialAppState extends State<MaterialApp> {
inspectorSelectButtonBuilder: _inspectorSelectButtonBuilder,
shortcuts: widget.shortcuts,
actions: widget.actions,
restorationScopeId: widget.restorationScopeId,
);
}
......
......@@ -19,6 +19,7 @@ import 'media_query.dart';
import 'navigator.dart';
import 'pages.dart';
import 'performance_overlay.dart';
import 'restoration.dart';
import 'router.dart';
import 'scrollable.dart';
import 'semantics_debugger.dart';
......@@ -195,6 +196,7 @@ class WidgetsApp extends StatefulWidget {
this.inspectorSelectButtonBuilder,
this.shortcuts,
this.actions,
this.restorationScopeId,
}) : assert(navigatorObservers != null),
assert(routes != null),
assert(
......@@ -290,6 +292,7 @@ class WidgetsApp extends StatefulWidget {
this.inspectorSelectButtonBuilder,
this.shortcuts,
this.actions,
this.restorationScopeId,
}) : assert(
routeInformationParser != null &&
routerDelegate != null,
......@@ -945,6 +948,24 @@ class WidgetsApp extends StatefulWidget {
/// {@endtemplate}
final Map<Type, Action<Intent>>? actions;
/// {@template flutter.widgets.widgetsApp.restorationScopeId}
/// The identifier to use for state restoration of this app.
///
/// Providing a restoration ID inserts a [RootRestorationScope] into the
/// widget hierarchy, which enables state restoration for descendant widgets.
///
/// Providing a restoration ID also enables the [Navigator] built by the
/// [WidgetsApp] to restore its state (i.e. to restore the history stack of
/// active [Route]s). See the documentation on [Navigator] for more details
/// around state restoration of [Route]s.
///
/// See also:
///
/// * [RestorationManager], which explains how state restoration works in
/// Flutter.
/// {@endtemplate}
final String? restorationScopeId;
/// If true, forces the performance overlay to be visible in all instances.
///
/// Used by the `showPerformanceOverlay` observatory extension.
......@@ -1467,6 +1488,7 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
} else {
assert(_navigator != null);
routing = Navigator(
restorationScopeId: 'nav',
key: _navigator,
initialRoute: _initialRouteName,
onGenerateRoute: _onGenerateRoute,
......@@ -1573,7 +1595,9 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
: _locale!;
assert(_debugCheckLocalizations(appLocale));
return Shortcuts(
return RootRestorationScope(
restorationId: widget.restorationScopeId,
child: Shortcuts(
shortcuts: widget.shortcuts ?? WidgetsApp.defaultShortcuts,
debugLabel: '<Default WidgetsApp Shortcuts>',
child: Actions(
......@@ -1589,6 +1613,7 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
),
),
),
),
);
}
}
......
......@@ -499,10 +499,13 @@ abstract class RestorableProperty<T> extends ChangeNotifier {
}
/// The [State] object that this property is registered with.
///
/// Must only be called when [isRegistered] is true.
@protected
State? get state {
State get state {
assert(isRegistered);
assert(_debugAssertNotDisposed());
return _owner;
return _owner!;
}
/// Whether this property is currently registered with a [RestorationMixin].
......@@ -609,13 +612,10 @@ abstract class RestorableProperty<T> extends ChangeNotifier {
/// class RestorationExampleApp extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// // The [RootRestorationScope] can be removed once it is part of [MaterialApp].
/// return RootRestorationScope(
/// restorationId: 'root',
/// child: MaterialApp(
/// return MaterialApp(
/// restorationScopeId: 'app',
/// title: 'Restorable Counter',
/// home: RestorableCounter(restorationId: 'counter'),
/// ),
/// );
/// }
/// }
......
......@@ -18,6 +18,7 @@ import 'modal_barrier.dart';
import 'navigator.dart';
import 'overlay.dart';
import 'page_storage.dart';
import 'restoration.dart';
import 'transitions.dart';
// Examples can assume:
......@@ -773,7 +774,16 @@ class _ModalScopeState<T> extends State<_ModalScope<T>> {
@override
Widget build(BuildContext context) {
return _ModalScopeStatus(
return AnimatedBuilder(
animation: widget.route.restorationScopeId,
builder: (BuildContext context, Widget? child) {
assert(child != null);
return RestorationScope(
restorationId: widget.route.restorationScopeId.value,
child: child!,
);
},
child: _ModalScopeStatus(
route: widget.route,
isCurrent: widget.route.isCurrent, // _routeSetState is called if this updates
canPop: widget.route.canPop, // _routeSetState is called if this updates
......@@ -829,6 +839,7 @@ class _ModalScopeState<T> extends State<_ModalScope<T>> {
),
),
),
),
);
}
}
......
......@@ -97,6 +97,10 @@ void main() {
expect(tester.takeException(), isFlutterError);
expect(unknownForRouteCalled, '/');
// Work-around for https://github.com/flutter/flutter/issues/65655.
await tester.pumpWidget(Container());
expect(tester.takeException(), isAssertionError);
});
testWidgets('Can use navigatorKey to navigate', (WidgetTester tester) async {
......
......@@ -16,9 +16,9 @@ const String alternativeText = 'Everything is awesome!!';
void main() {
testWidgets('CupertinoTextField restoration', (WidgetTester tester) async {
await tester.pumpWidget(
const RootRestorationScope(
child: TestWidget(),
restorationId: 'root',
const CupertinoApp(
restorationScopeId: 'app',
home: TestWidget(),
),
);
......@@ -27,11 +27,11 @@ void main() {
testWidgets('CupertinoTextField restoration with external controller', (WidgetTester tester) async {
await tester.pumpWidget(
const RootRestorationScope(
child: TestWidget(
const CupertinoApp(
restorationScopeId: 'app',
home: TestWidget(
useExternal: true,
),
restorationId: 'root',
),
);
......@@ -102,8 +102,7 @@ class TestWidgetState extends State<TestWidget> with RestorationMixin {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Material(
return Material(
child: Align(
alignment: Alignment.center,
child: SizedBox(
......@@ -115,7 +114,6 @@ class TestWidgetState extends State<TestWidget> with RestorationMixin {
),
),
),
),
);
}
}
......
......@@ -384,6 +384,10 @@ void main() {
);
expect(tester.takeException(), isFlutterError);
expect(log, <String>['onGenerateRoute /', 'onUnknownRoute /']);
// Work-around for https://github.com/flutter/flutter/issues/65655.
await tester.pumpWidget(Container());
expect(tester.takeException(), isAssertionError);
});
testWidgets('MaterialApp with builder and no route information works.', (WidgetTester tester) async {
......
......@@ -142,6 +142,9 @@ void main() {
' PageStorage\n'
' Offstage\n'
' _ModalScopeStatus\n'
' UnmanagedRestorationScope\n'
' RestorationScope\n'
' AnimatedBuilder\n'
' _ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#00000]\n'
' Semantics\n'
' _EffectiveTickerMode\n'
......@@ -149,6 +152,7 @@ void main() {
' _OverlayEntryWidget-[LabeledGlobalKey<_OverlayEntryWidgetState>#00000]\n'
' _Theatre\n'
' Overlay-[LabeledGlobalKey<OverlayState>#00000]\n'
' UnmanagedRestorationScope\n'
' _FocusMarker\n'
' Semantics\n'
' FocusScope\n'
......@@ -187,6 +191,10 @@ void main() {
' _FocusMarker\n'
' Focus\n'
' Shortcuts\n'
' UnmanagedRestorationScope\n'
' RestorationScope\n'
' UnmanagedRestorationScope\n'
' RootRestorationScope\n'
' WidgetsApp-[GlobalObjectKey _MaterialAppState#00000]\n'
' HeroControllerScope\n'
' ScrollConfiguration\n'
......
......@@ -15,9 +15,9 @@ const String alternativeText = 'Everything is awesome!!';
void main() {
testWidgets('TextField restoration', (WidgetTester tester) async {
await tester.pumpWidget(
const RootRestorationScope(
child: TestWidget(),
restorationId: 'root',
const MaterialApp(
restorationScopeId: 'app',
home: TestWidget(),
),
);
......@@ -26,11 +26,11 @@ void main() {
testWidgets('TextField restoration with external controller', (WidgetTester tester) async {
await tester.pumpWidget(
const RootRestorationScope(
child: TestWidget(
const MaterialApp(
restorationScopeId: 'root',
home: TestWidget(
useExternal: true,
),
restorationId: 'root',
),
);
......@@ -101,8 +101,7 @@ class TestWidgetState extends State<TestWidget> with RestorationMixin {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Material(
return Material(
child: Align(
alignment: Alignment.center,
child: SizedBox(
......@@ -114,7 +113,6 @@ class TestWidgetState extends State<TestWidget> with RestorationMixin {
),
),
),
),
);
}
}
......
This diff is collapsed.
......@@ -1664,7 +1664,7 @@ void main() {
' The onGenerateRoute callback must never return null, unless an\n'
' onUnknownRoute callback is provided as well.\n'
' The Navigator was:\n'
' NavigatorState#4d6bf(lifecycle state: created)\n',
' NavigatorState#00000(lifecycle state: initialized)\n'
),
);
});
......@@ -1690,7 +1690,7 @@ void main() {
' route "/".\n'
' The onUnknownRoute callback must never return null.\n'
' The Navigator was:\n'
' NavigatorState#38036(lifecycle state: created)\n',
' NavigatorState#00000(lifecycle state: initialized)\n',
),
);
});
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment