Unverified Commit 062e469f authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Page-subclasses to take children instead of builder (#66694)

parent b8397f69
...@@ -93,7 +93,8 @@ final DecorationTween _kGradientShadowTween = DecorationTween( ...@@ -93,7 +93,8 @@ final DecorationTween _kGradientShadowTween = DecorationTween(
/// * [CupertinoPageRoute], which is a [PageRoute] that leverages this mixin. /// * [CupertinoPageRoute], which is a [PageRoute] that leverages this mixin.
mixin CupertinoRouteTransitionMixin<T> on PageRoute<T> { mixin CupertinoRouteTransitionMixin<T> on PageRoute<T> {
/// Builds the primary contents of the route. /// Builds the primary contents of the route.
WidgetBuilder get builder; @protected
Widget buildContent(BuildContext context);
/// {@template flutter.cupertino.cupertinoRouteTransitionMixin.title} /// {@template flutter.cupertino.cupertinoRouteTransitionMixin.title}
/// A title string for this route. /// A title string for this route.
...@@ -224,7 +225,7 @@ mixin CupertinoRouteTransitionMixin<T> on PageRoute<T> { ...@@ -224,7 +225,7 @@ mixin CupertinoRouteTransitionMixin<T> on PageRoute<T> {
@override @override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
final Widget child = builder(context); final Widget child = buildContent(context);
final Widget result = Semantics( final Widget result = Semantics(
scopesRoute: true, scopesRoute: true,
explicitChildNodes: true, explicitChildNodes: true,
...@@ -351,9 +352,12 @@ class CupertinoPageRoute<T> extends PageRoute<T> with CupertinoRouteTransitionMi ...@@ -351,9 +352,12 @@ class CupertinoPageRoute<T> extends PageRoute<T> with CupertinoRouteTransitionMi
assert(opaque), assert(opaque),
super(settings: settings, fullscreenDialog: fullscreenDialog); super(settings: settings, fullscreenDialog: fullscreenDialog);
@override /// Builds the primary contents of the route.
final WidgetBuilder builder; final WidgetBuilder builder;
@override
Widget buildContent(BuildContext context) => builder(context);
@override @override
final String? title; final String? title;
...@@ -378,7 +382,7 @@ class _PageBasedCupertinoPageRoute<T> extends PageRoute<T> with CupertinoRouteTr ...@@ -378,7 +382,7 @@ class _PageBasedCupertinoPageRoute<T> extends PageRoute<T> with CupertinoRouteTr
CupertinoPage<T> get _page => settings as CupertinoPage<T>; CupertinoPage<T> get _page => settings as CupertinoPage<T>;
@override @override
WidgetBuilder get builder => _page.builder; Widget buildContent(BuildContext context) => _page.child;
@override @override
String? get title => _page.title; String? get title => _page.title;
...@@ -412,20 +416,20 @@ class _PageBasedCupertinoPageRoute<T> extends PageRoute<T> with CupertinoRouteTr ...@@ -412,20 +416,20 @@ class _PageBasedCupertinoPageRoute<T> extends PageRoute<T> with CupertinoRouteTr
class CupertinoPage<T> extends Page<T> { class CupertinoPage<T> extends Page<T> {
/// Creates a cupertino page. /// Creates a cupertino page.
const CupertinoPage({ const CupertinoPage({
required this.builder, required this.child,
this.maintainState = true, this.maintainState = true,
this.title, this.title,
this.fullscreenDialog = false, this.fullscreenDialog = false,
LocalKey? key, LocalKey? key,
String? name, String? name,
Object? arguments, Object? arguments,
}) : assert(builder != null), }) : assert(child != null),
assert(maintainState != null), assert(maintainState != null),
assert(fullscreenDialog != null), assert(fullscreenDialog != null),
super(key: key, name: name, arguments: arguments); super(key: key, name: name, arguments: arguments);
/// Builds the primary contents of the route. /// The content to be shown in the [Route] created by this page.
final WidgetBuilder builder; final Widget child;
/// {@macro flutter.cupertino.cupertinoRouteTransitionMixin.title} /// {@macro flutter.cupertino.cupertinoRouteTransitionMixin.title}
final String? title; final String? title;
......
...@@ -48,9 +48,12 @@ class MaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixi ...@@ -48,9 +48,12 @@ class MaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixi
assert(opaque), assert(opaque),
super(settings: settings, fullscreenDialog: fullscreenDialog); super(settings: settings, fullscreenDialog: fullscreenDialog);
@override /// Builds the primary contents of the route.
final WidgetBuilder builder; final WidgetBuilder builder;
@override
Widget buildContent(BuildContext context) => builder(context);
@override @override
final bool maintainState; final bool maintainState;
...@@ -77,7 +80,8 @@ class MaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixi ...@@ -77,7 +80,8 @@ class MaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixi
/// by the [MaterialRouteTransitionMixin.buildTransitions]. /// by the [MaterialRouteTransitionMixin.buildTransitions].
mixin MaterialRouteTransitionMixin<T> on PageRoute<T> { mixin MaterialRouteTransitionMixin<T> on PageRoute<T> {
/// Builds the primary contents of the route. /// Builds the primary contents of the route.
WidgetBuilder get builder; @protected
Widget buildContent(BuildContext context);
@override @override
Duration get transitionDuration => const Duration(milliseconds: 300); Duration get transitionDuration => const Duration(milliseconds: 300);
...@@ -101,7 +105,7 @@ mixin MaterialRouteTransitionMixin<T> on PageRoute<T> { ...@@ -101,7 +105,7 @@ mixin MaterialRouteTransitionMixin<T> on PageRoute<T> {
Animation<double> animation, Animation<double> animation,
Animation<double> secondaryAnimation, Animation<double> secondaryAnimation,
) { ) {
final Widget result = builder(context); final Widget result = buildContent(context);
assert(() { assert(() {
if (result == null) { if (result == null) {
throw FlutterError( throw FlutterError(
...@@ -148,19 +152,19 @@ mixin MaterialRouteTransitionMixin<T> on PageRoute<T> { ...@@ -148,19 +152,19 @@ mixin MaterialRouteTransitionMixin<T> on PageRoute<T> {
class MaterialPage<T> extends Page<T> { class MaterialPage<T> extends Page<T> {
/// Creates a material page. /// Creates a material page.
const MaterialPage({ const MaterialPage({
@required this.builder, @required this.child,
this.maintainState = true, this.maintainState = true,
this.fullscreenDialog = false, this.fullscreenDialog = false,
LocalKey key, LocalKey key,
String name, String name,
Object arguments, Object arguments,
}) : assert(builder != null), }) : assert(child != null),
assert(maintainState != null), assert(maintainState != null),
assert(fullscreenDialog != null), assert(fullscreenDialog != null),
super(key: key, name: name, arguments: arguments); super(key: key, name: name, arguments: arguments);
/// Builds the primary contents of the route. /// The content to be shown in the [Route] created by this page.
final WidgetBuilder builder; final Widget child;
/// {@macro flutter.widgets.modalRoute.maintainState} /// {@macro flutter.widgets.modalRoute.maintainState}
final bool maintainState; final bool maintainState;
...@@ -188,7 +192,9 @@ class _PageBasedMaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTran ...@@ -188,7 +192,9 @@ class _PageBasedMaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTran
MaterialPage<T> get _page => settings as MaterialPage<T>; MaterialPage<T> get _page => settings as MaterialPage<T>;
@override @override
WidgetBuilder get builder => _page.builder; Widget buildContent(BuildContext context) {
return _page.child;
}
@override @override
bool get maintainState => _page.maintainState; bool get maintainState => _page.maintainState;
......
...@@ -37,11 +37,6 @@ import 'ticker_provider.dart'; ...@@ -37,11 +37,6 @@ import 'ticker_provider.dart';
/// * [Navigator], which is where all the [Route]s end up. /// * [Navigator], which is where all the [Route]s end up.
typedef RouteFactory = Route<dynamic>? Function(RouteSettings settings); typedef RouteFactory = Route<dynamic>? Function(RouteSettings settings);
/// Creates a route for the given context and route settings.
///
/// Used by [CustomBuilderPage.routeBuilder].
typedef RouteBuilder<T> = Route<T> Function(BuildContext context, RouteSettings settings);
/// Creates a series of one or more routes. /// Creates a series of one or more routes.
/// ///
/// Used by [Navigator.onGenerateInitialRoutes]. /// Used by [Navigator.onGenerateInitialRoutes].
...@@ -507,8 +502,6 @@ class RouteSettings { ...@@ -507,8 +502,6 @@ class RouteSettings {
/// ///
/// * [Navigator.pages], which accepts a list of [Page]s and updates its routes /// * [Navigator.pages], which accepts a list of [Page]s and updates its routes
/// history. /// history.
/// * [CustomBuilderPage], a [Page] subclass that provides the API to build a
/// customized route.
abstract class Page<T> extends RouteSettings { abstract class Page<T> extends RouteSettings {
/// Creates a page and initializes [key] for subclasses. /// Creates a page and initializes [key] for subclasses.
/// ///
...@@ -543,38 +536,6 @@ abstract class Page<T> extends RouteSettings { ...@@ -543,38 +536,6 @@ abstract class Page<T> extends RouteSettings {
String toString() => '${objectRuntimeType(this, 'Page')}("$name", $key, $arguments)'; String toString() => '${objectRuntimeType(this, 'Page')}("$name", $key, $arguments)';
} }
/// A [Page] that builds a customized [Route] based on the [routeBuilder].
///
/// The type argument `T` is the corresponding [Route]'s return type, as
/// used by [Route.currentResult], [Route.popped], and [Route.didPop].
class CustomBuilderPage<T> extends Page<T> {
/// Creates a page with a custom route builder.
///
/// Use [routeBuilder] to specify the route that will be created from this
/// page.
const CustomBuilderPage({
required LocalKey key,
required this.routeBuilder,
String? name,
Object? arguments,
}) : assert(key != null),
assert(routeBuilder != null),
super(key: key, name: name, arguments: arguments);
/// A builder that will be called during [createRoute] to create a [Route].
///
/// The routes returned from this builder must have their settings equal to
/// the input `settings`.
final RouteBuilder<T> routeBuilder;
@override
Route<T> createRoute(BuildContext context) {
final Route<T> route = routeBuilder(context, this);
assert(route.settings == this);
return route;
}
}
/// An interface for observing the behavior of a [Navigator]. /// An interface for observing the behavior of a [Navigator].
class NavigatorObserver { class NavigatorObserver {
/// The navigator that the observer is observing, if any. /// The navigator that the observer is observing, if any.
...@@ -1045,10 +1006,10 @@ class DefaultTransitionDelegate<T> extends TransitionDelegate<T> { ...@@ -1045,10 +1006,10 @@ class DefaultTransitionDelegate<T> extends TransitionDelegate<T> {
/// The [Navigator] will convert its [Navigator.pages] into a stack of [Route]s /// The [Navigator] will convert its [Navigator.pages] into a stack of [Route]s
/// if it is provided. A change in [Navigator.pages] will trigger an update to /// if it is provided. A change in [Navigator.pages] will trigger an update to
/// the stack of [Route]s. The [Navigator] will update its routes to match the /// the stack of [Route]s. The [Navigator] will update its routes to match the
/// new configuration of its [Navigator.pages]. To use this API, one can use /// new configuration of its [Navigator.pages]. To use this API, one can create
/// [CustomBuilderPage] or create a [Page] subclass and defines a list of /// a [Page] subclass and defines a list of [Page]s for [Navigator.pages]. A
/// [Page]s for [Navigator.pages]. A [Navigator.onPopPage] callback is also /// [Navigator.onPopPage] callback is also required to properly clean up the
/// required to properly clean up the input pages in case of a pop. /// input pages in case of a pop.
/// ///
/// By Default, the [Navigator] will use [DefaultTransitionDelegate] to decide /// By Default, the [Navigator] will use [DefaultTransitionDelegate] to decide
/// how routes transition in or out of the screen. To customize it, define a /// how routes transition in or out of the screen. To customize it, define a
......
...@@ -46,10 +46,6 @@ Widget _defaultTransitionsBuilder(BuildContext context, Animation<double> animat ...@@ -46,10 +46,6 @@ Widget _defaultTransitionsBuilder(BuildContext context, Animation<double> animat
/// ///
/// Callers must define the [pageBuilder] function which creates the route's /// Callers must define the [pageBuilder] function which creates the route's
/// primary contents. To add transitions define the [transitionsBuilder] function. /// primary contents. To add transitions define the [transitionsBuilder] function.
///
/// See also:
///
/// * [TransitionBuilderPage], which is a [Page] of this class.
class PageRouteBuilder<T> extends PageRoute<T> { class PageRouteBuilder<T> extends PageRoute<T> {
/// Creates a route that delegates to builder callbacks. /// Creates a route that delegates to builder callbacks.
/// ///
...@@ -120,126 +116,3 @@ class PageRouteBuilder<T> extends PageRoute<T> { ...@@ -120,126 +116,3 @@ class PageRouteBuilder<T> extends PageRoute<T> {
return transitionsBuilder(context, animation, secondaryAnimation, child); return transitionsBuilder(context, animation, secondaryAnimation, child);
} }
} }
/// A page that creates a [PageRoute] with customizable transition.
///
/// Similar to the [PageRouteBuilder], callers must define the [pageBuilder]
/// function which creates the route's primary contents. To add transitions
/// define the [transitionsBuilder] function.
///
/// See also:
///
/// * [PageRouteBuilder], which is a [PageRoute] version of this class.
class TransitionBuilderPage<T> extends Page<T> {
/// Creates a [TransitionBuilderPage].
const TransitionBuilderPage({
required this.pageBuilder,
this.transitionsBuilder = _defaultTransitionsBuilder,
this.transitionDuration = const Duration(milliseconds: 300),
this.reverseTransitionDuration = const Duration(milliseconds: 300),
this.opaque = true,
this.barrierDismissible = false,
this.barrierColor,
this.barrierLabel,
this.maintainState = true,
this.fullscreenDialog = false,
LocalKey? key,
String? name,
Object? arguments,
}) : assert(pageBuilder != null),
assert(transitionsBuilder != null),
assert(opaque != null),
assert(barrierDismissible != null),
assert(maintainState != null),
assert(fullscreenDialog != null),
super(key: key, name: name, arguments: arguments);
/// {@macro flutter.widgets.pageRouteBuilder.pageBuilder}
final RoutePageBuilder pageBuilder;
/// {@macro flutter.widgets.pageRouteBuilder.transitionsBuilder}
final RouteTransitionsBuilder transitionsBuilder;
/// {@macro flutter.widgets.transitionRoute.transitionDuration}
final Duration transitionDuration;
/// {@macro flutter.widgets.transitionRoute.reverseTransitionDuration}
final Duration reverseTransitionDuration;
/// {@macro flutter.widgets.transitionRoute.opaque}
final bool opaque;
/// {@macro flutter.widgets.modalRoute.barrierDismissible}
final bool barrierDismissible;
/// {@macro flutter.widgets.modalRoute.barrierColor}
///
/// See also:
///
/// * [barrierDismissible], which controls the behavior of the barrier when
/// tapped.
/// * [ModalBarrier], the widget that implements this feature.
final Color? barrierColor;
/// {@macro flutter.widgets.modalRoute.barrierLabel}
///
/// See also:
///
/// * [barrierDismissible], which controls the behavior of the barrier when
/// tapped.
/// * [ModalBarrier], the widget that implements this feature.
final String? barrierLabel;
/// {@macro flutter.widgets.modalRoute.maintainState}
final bool maintainState;
/// {@macro flutter.widgets.pageRoute.fullscreenDialog}
final bool fullscreenDialog;
@override
Route<T> createRoute(BuildContext context) => _PageBasedPageRouteBuilder<T>(this);
}
// A page-based version of the [PageRouteBuilder].
//
// This class gets its builder and settings directly from the [TransitionBuilderPage],
// so that its content updates accordingly to the [TransitionBuilderPage].
class _PageBasedPageRouteBuilder<T> extends PageRoute<T>{
_PageBasedPageRouteBuilder(
TransitionBuilderPage<T> page,
) : assert(page != null),
super(settings: page, fullscreenDialog: page.fullscreenDialog);
TransitionBuilderPage<T> get _page => settings as TransitionBuilderPage<T>;
@override
Duration get transitionDuration => _page.transitionDuration;
@override
Duration get reverseTransitionDuration => _page.reverseTransitionDuration;
@override
bool get opaque => _page.opaque;
@override
bool get barrierDismissible => _page.barrierDismissible;
@override
Color? get barrierColor => _page.barrierColor;
@override
String? get barrierLabel => _page.barrierLabel;
@override
bool get maintainState => _page.maintainState;
@override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return _page.pageBuilder(context, animation, secondaryAnimation);
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
return _page.transitionsBuilder(context, animation, secondaryAnimation, child);
}
}
...@@ -236,12 +236,12 @@ class SimpleNavigatorRouterDelegate extends RouterDelegate<RouteInformation> wit ...@@ -236,12 +236,12 @@ class SimpleNavigatorRouterDelegate extends RouterDelegate<RouteInformation> wit
pages: <Page<void>>[ pages: <Page<void>>[
// We need at least two pages for the pop to propagate through. // We need at least two pages for the pop to propagate through.
// Otherwise, the navigator will bubble the pop to the system navigator. // Otherwise, the navigator will bubble the pop to the system navigator.
CupertinoPage<void>( const CupertinoPage<void>(
builder: (BuildContext context) => const Text('base'), child: Text('base'),
), ),
CupertinoPage<void>( CupertinoPage<void>(
key: ValueKey<String>(routeInformation?.location), key: ValueKey<String>(routeInformation?.location),
builder: (BuildContext context) => builder(context, routeInformation), child: builder(context, routeInformation),
) )
], ],
); );
......
...@@ -464,6 +464,19 @@ void main() { ...@@ -464,6 +464,19 @@ void main() {
expect(find.text('Page 1'), findsNothing); expect(find.text('Page 1'), findsNothing);
expect(find.text('Page 2'), isOnstage); expect(find.text('Page 2'), isOnstage);
}); });
testWidgets('CupertinoPage does not lose its state when transitioning out', (WidgetTester tester) async {
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
await tester.pumpWidget(KeepsStateTestWidget(navigatorKey: navigator));
expect(find.text('subpage'), findsOneWidget);
expect(find.text('home'), findsNothing);
navigator.currentState.pop();
await tester.pump();
expect(find.text('subpage'), findsOneWidget);
expect(find.text('home'), findsOneWidget);
});
} }
class RtlOverrideWidgetsDelegate extends LocalizationsDelegate<WidgetsLocalizations> { class RtlOverrideWidgetsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
...@@ -481,3 +494,38 @@ class RtlOverrideWidgetsLocalization implements WidgetsLocalizations { ...@@ -481,3 +494,38 @@ class RtlOverrideWidgetsLocalization implements WidgetsLocalizations {
@override @override
TextDirection get textDirection => TextDirection.rtl; TextDirection get textDirection => TextDirection.rtl;
} }
class KeepsStateTestWidget extends StatefulWidget {
const KeepsStateTestWidget({this.navigatorKey});
final Key navigatorKey;
@override
State<KeepsStateTestWidget> createState() => _KeepsStateTestWidgetState();
}
class _KeepsStateTestWidgetState extends State<KeepsStateTestWidget> {
String _subpage = 'subpage';
@override
Widget build(BuildContext context) {
return CupertinoApp(
home: Navigator(
key: widget.navigatorKey,
pages: <Page<void>>[
const CupertinoPage<void>(child: Text('home')),
if (_subpage != null) CupertinoPage<void>(child: Text(_subpage)),
],
onPopPage: (Route<dynamic> route, dynamic result) {
if (!route.didPop(result)) {
return false;
}
setState(() {
_subpage = null;
});
return true;
},
),
);
}
}
...@@ -1382,12 +1382,10 @@ void main() { ...@@ -1382,12 +1382,10 @@ void main() {
CupertinoPage<void>( CupertinoPage<void>(
key: pageKey, key: pageKey,
title: 'title one', title: 'title one',
builder: (BuildContext context) { child: CupertinoPageScaffold(
return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar(key: UniqueKey()),
navigationBar: CupertinoNavigationBar(key: UniqueKey()), child: const Text('first'),
child: const Text('first'), ),
);
}
), ),
]; ];
await tester.pumpWidget( await tester.pumpWidget(
...@@ -1406,12 +1404,10 @@ void main() { ...@@ -1406,12 +1404,10 @@ void main() {
CupertinoPage<void>( CupertinoPage<void>(
key: pageKey, key: pageKey,
title: 'title two', title: 'title two',
builder: (BuildContext context) { child: CupertinoPageScaffold(
return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar(key: UniqueKey()),
navigationBar: CupertinoNavigationBar(key: UniqueKey()), child: const Text('second'),
child: const Text('second'), ),
);
}
), ),
]; ];
...@@ -1437,8 +1433,8 @@ void main() { ...@@ -1437,8 +1433,8 @@ void main() {
final LocalKey pageKeyTwo = UniqueKey(); final LocalKey pageKeyTwo = UniqueKey();
final TransitionDetector detector = TransitionDetector(); final TransitionDetector detector = TransitionDetector();
List<Page<void>> myPages = <Page<void>>[ List<Page<void>> myPages = <Page<void>>[
CupertinoPage<void>(key: pageKeyOne, maintainState: false, builder: (BuildContext context) => const Text('first')), CupertinoPage<void>(key: pageKeyOne, maintainState: false, child: const Text('first')),
CupertinoPage<void>(key: pageKeyTwo, builder: (BuildContext context) => const Text('second')), CupertinoPage<void>(key: pageKeyTwo, child: const Text('second')),
]; ];
await tester.pumpWidget( await tester.pumpWidget(
buildNavigator( buildNavigator(
...@@ -1454,8 +1450,8 @@ void main() { ...@@ -1454,8 +1450,8 @@ void main() {
expect(find.text('second'), findsOneWidget); expect(find.text('second'), findsOneWidget);
myPages = <Page<void>>[ myPages = <Page<void>>[
CupertinoPage<void>(key: pageKeyOne, maintainState: true, builder: (BuildContext context) => const Text('first')), CupertinoPage<void>(key: pageKeyOne, maintainState: true, child: const Text('first')),
CupertinoPage<void>(key: pageKeyTwo, builder: (BuildContext context) => const Text('second')), CupertinoPage<void>(key: pageKeyTwo, child: const Text('second')),
]; ];
await tester.pumpWidget( await tester.pumpWidget(
......
...@@ -1056,12 +1056,12 @@ class SimpleNavigatorRouterDelegate extends RouterDelegate<RouteInformation> wit ...@@ -1056,12 +1056,12 @@ class SimpleNavigatorRouterDelegate extends RouterDelegate<RouteInformation> wit
pages: <Page<void>>[ pages: <Page<void>>[
// We need at least two pages for the pop to propagate through. // We need at least two pages for the pop to propagate through.
// Otherwise, the navigator will bubble the pop to the system navigator. // Otherwise, the navigator will bubble the pop to the system navigator.
MaterialPage<void>( const MaterialPage<void>(
builder: (BuildContext context) => const Text('base'), child: Text('base'),
), ),
MaterialPage<void>( MaterialPage<void>(
key: ValueKey<String>(routeInformation?.location), key: ValueKey<String>(routeInformation?.location),
builder: (BuildContext context) => builder(context, routeInformation), child: builder(context, routeInformation),
) )
], ],
); );
......
...@@ -841,7 +841,7 @@ void main() { ...@@ -841,7 +841,7 @@ void main() {
final LocalKey pageKey = UniqueKey(); final LocalKey pageKey = UniqueKey();
final TransitionDetector detector = TransitionDetector(); final TransitionDetector detector = TransitionDetector();
List<Page<void>> myPages = <Page<void>>[ List<Page<void>> myPages = <Page<void>>[
MaterialPage<void>(key: pageKey, builder: (BuildContext context) => const Text('first')), MaterialPage<void>(key: pageKey, child: const Text('first')),
]; ];
await tester.pumpWidget( await tester.pumpWidget(
buildNavigator( buildNavigator(
...@@ -855,7 +855,7 @@ void main() { ...@@ -855,7 +855,7 @@ void main() {
expect(find.text('first'), findsOneWidget); expect(find.text('first'), findsOneWidget);
myPages = <Page<void>>[ myPages = <Page<void>>[
MaterialPage<void>(key: pageKey, builder: (BuildContext context) => const Text('second')), MaterialPage<void>(key: pageKey, child: const Text('second')),
]; ];
await tester.pumpWidget( await tester.pumpWidget(
...@@ -877,8 +877,8 @@ void main() { ...@@ -877,8 +877,8 @@ void main() {
final LocalKey pageKeyTwo = UniqueKey(); final LocalKey pageKeyTwo = UniqueKey();
final TransitionDetector detector = TransitionDetector(); final TransitionDetector detector = TransitionDetector();
List<Page<void>> myPages = <Page<void>>[ List<Page<void>> myPages = <Page<void>>[
MaterialPage<void>(key: pageKeyOne, maintainState: false, builder: (BuildContext context) => const Text('first')), MaterialPage<void>(key: pageKeyOne, maintainState: false, child: const Text('first')),
MaterialPage<void>(key: pageKeyTwo, builder: (BuildContext context) => const Text('second')), MaterialPage<void>(key: pageKeyTwo, child: const Text('second')),
]; ];
await tester.pumpWidget( await tester.pumpWidget(
buildNavigator( buildNavigator(
...@@ -894,8 +894,8 @@ void main() { ...@@ -894,8 +894,8 @@ void main() {
expect(find.text('second'), findsOneWidget); expect(find.text('second'), findsOneWidget);
myPages = <Page<void>>[ myPages = <Page<void>>[
MaterialPage<void>(key: pageKeyOne, maintainState: true, builder: (BuildContext context) => const Text('first')), MaterialPage<void>(key: pageKeyOne, maintainState: true, child: const Text('first')),
MaterialPage<void>(key: pageKeyTwo, builder: (BuildContext context) => const Text('second')), MaterialPage<void>(key: pageKeyTwo, child: const Text('second')),
]; ];
await tester.pumpWidget( await tester.pumpWidget(
...@@ -912,6 +912,19 @@ void main() { ...@@ -912,6 +912,19 @@ void main() {
expect(find.text('first', skipOffstage: false), findsOneWidget); expect(find.text('first', skipOffstage: false), findsOneWidget);
expect(find.text('second'), findsOneWidget); expect(find.text('second'), findsOneWidget);
}); });
testWidgets('MaterialPage does not lose its state when transitioning out', (WidgetTester tester) async {
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
await tester.pumpWidget(KeepsStateTestWidget(navigatorKey: navigator));
expect(find.text('subpage'), findsOneWidget);
expect(find.text('home'), findsNothing);
navigator.currentState.pop();
await tester.pump();
expect(find.text('subpage'), findsOneWidget);
expect(find.text('home'), findsOneWidget);
});
} }
class TransitionDetector extends DefaultTransitionDelegate<void> { class TransitionDetector extends DefaultTransitionDelegate<void> {
...@@ -957,3 +970,38 @@ Widget buildNavigator({ ...@@ -957,3 +970,38 @@ Widget buildNavigator({
), ),
); );
} }
class KeepsStateTestWidget extends StatefulWidget {
const KeepsStateTestWidget({this.navigatorKey});
final Key navigatorKey;
@override
State<KeepsStateTestWidget> createState() => _KeepsStateTestWidgetState();
}
class _KeepsStateTestWidgetState extends State<KeepsStateTestWidget> {
String _subpage = 'subpage';
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Navigator(
key: widget.navigatorKey,
pages: <Page<void>>[
const MaterialPage<void>(child: Text('home')),
if (_subpage != null) MaterialPage<void>(child: Text(_subpage)),
],
onPopPage: (Route<dynamic> route, dynamic result) {
if (!route.didPop(result)) {
return false;
}
setState(() {
_subpage = null;
});
return true;
},
),
);
}
}
...@@ -366,12 +366,12 @@ class SimpleNavigatorRouterDelegate extends RouterDelegate<RouteInformation> wit ...@@ -366,12 +366,12 @@ class SimpleNavigatorRouterDelegate extends RouterDelegate<RouteInformation> wit
pages: <Page<void>>[ pages: <Page<void>>[
// We need at least two pages for the pop to propagate through. // We need at least two pages for the pop to propagate through.
// Otherwise, the navigator will bubble the pop to the system navigator. // Otherwise, the navigator will bubble the pop to the system navigator.
MaterialPage<void>( const MaterialPage<void>(
builder: (BuildContext context) => const Text('base'), child: Text('base'),
), ),
MaterialPage<void>( MaterialPage<void>(
key: ValueKey<String>(routeInformation?.location), key: ValueKey<String>(routeInformation?.location),
builder: (BuildContext context) => builder(context, routeInformation), child: builder(context, routeInformation),
) )
], ],
); );
......
...@@ -119,11 +119,8 @@ void main() { ...@@ -119,11 +119,8 @@ void main() {
await tester.pumpWidget(Directionality( await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: Navigator( child: Navigator(
pages: <Page<void>>[ pages: const <Page<void>>[
TransitionBuilderPage<void>( TestPage(name: '/'),
name: '/',
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => const Placeholder(),
),
], ],
onPopPage: (Route<void> route, void result) => false, onPopPage: (Route<void> route, void result) => false,
) )
...@@ -134,15 +131,9 @@ void main() { ...@@ -134,15 +131,9 @@ void main() {
await tester.pumpWidget(Directionality( await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: Navigator( child: Navigator(
pages: <Page<void>>[ pages: const <Page<void>>[
TransitionBuilderPage<void>( TestPage(name: '/'),
name: '/', TestPage(name: '/abc',),
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => const Placeholder(),
),
TransitionBuilderPage<void>(
name: '/abc',
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => const Placeholder(),
),
], ],
onPopPage: (Route<void> route, void result) => false, onPopPage: (Route<void> route, void result) => false,
) )
...@@ -371,3 +362,15 @@ class SimpleRouterDelegate extends RouterDelegate<RouteInformation> with ChangeN ...@@ -371,3 +362,15 @@ class SimpleRouterDelegate extends RouterDelegate<RouteInformation> with ChangeN
@override @override
Widget build(BuildContext context) => builder(context, routeInformation); Widget build(BuildContext context) => builder(context, routeInformation);
} }
class TestPage extends Page<void> {
const TestPage({LocalKey key, String name}) : super(key: key, name: name);
@override
Route<void> createRoute(BuildContext context) {
return PageRouteBuilder<void>(
settings: this,
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => const Placeholder(),
);
}
}
...@@ -663,12 +663,12 @@ class SimpleNavigatorRouterDelegate extends RouterDelegate<RouteInformation> wit ...@@ -663,12 +663,12 @@ class SimpleNavigatorRouterDelegate extends RouterDelegate<RouteInformation> wit
pages: <Page<void>>[ pages: <Page<void>>[
// We need at least two pages for the pop to propagate through. // We need at least two pages for the pop to propagate through.
// Otherwise, the navigator will bubble the pop to the system navigator. // Otherwise, the navigator will bubble the pop to the system navigator.
MaterialPage<void>( const MaterialPage<void>(
builder: (BuildContext context) => const Text('base'), child: Text('base'),
), ),
MaterialPage<void>( MaterialPage<void>(
key: ValueKey<String>(routeInformation?.location), key: ValueKey<String>(routeInformation?.location),
builder: (BuildContext context) => builder(context, routeInformation), child: builder(context, routeInformation),
) )
], ],
); );
......
...@@ -542,142 +542,6 @@ void main() { ...@@ -542,142 +542,6 @@ void main() {
expect(focusNode.hasPrimaryFocus, isTrue); expect(focusNode.hasPrimaryFocus, isTrue);
}); });
testWidgets('TransitionBuilderPage works', (WidgetTester tester) async {
final LocalKey pageKey = UniqueKey();
final TransitionDetector detector = TransitionDetector();
List<Page<void>> myPages = <Page<void>>[
TransitionBuilderPage<void>(
key: pageKey,
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return const Text('first');
},
)
];
await tester.pumpWidget(
buildNavigator(
pages: myPages,
onPopPage: (Route<dynamic> route, dynamic result) => null,
transitionDelegate: detector,
)
);
expect(detector.hasTransition, isFalse);
expect(find.text('first'), findsOneWidget);
myPages = <Page<void>>[
TransitionBuilderPage<void>(
key: pageKey,
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return const Text('second');
},
)
];
await tester.pumpWidget(
buildNavigator(
pages: myPages,
onPopPage: (Route<dynamic> route, dynamic result) => null,
transitionDelegate: detector,
)
);
// There should be no transition because the page has the same key.
expect(detector.hasTransition, isFalse);
// The content does update.
expect(find.text('first'), findsNothing);
expect(find.text('second'), findsOneWidget);
myPages = <Page<void>>[
TransitionBuilderPage<void>(
key: pageKey,
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return const Text('dummy');
},
transitionsBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
// Purposely discard the input child.
return const Text('third');
}
)
];
await tester.pumpWidget(
buildNavigator(
pages: myPages,
onPopPage: (Route<dynamic> route, dynamic result) => null,
transitionDelegate: detector,
)
);
// There should be no transition because the page has the same key.
expect(detector.hasTransition, isFalse);
// Makes sure transitionsBuilder works.
expect(find.text('second'), findsNothing);
expect(find.text('dummy'), findsNothing);
expect(find.text('third'), findsOneWidget);
});
testWidgets('TransitionBuilderPage can toggle MaintainState', (WidgetTester tester) async {
final LocalKey pageKeyOne = UniqueKey();
final LocalKey pageKeyTwo = UniqueKey();
final TransitionDetector detector = TransitionDetector();
List<Page<void>> myPages = <Page<void>>[
TransitionBuilderPage<void>(
key: pageKeyOne,
maintainState: false,
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return const Text('first');
},
),
TransitionBuilderPage<void>(
key: pageKeyTwo,
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return const Text('second');
},
)
];
await tester.pumpWidget(
buildNavigator(
pages: myPages,
onPopPage: (Route<dynamic> route, dynamic result) => null,
transitionDelegate: detector,
)
);
expect(detector.hasTransition, isFalse);
// Page one does not maintain state.
expect(find.text('first', skipOffstage: false), findsNothing);
expect(find.text('second'), findsOneWidget);
myPages = <Page<void>>[
TransitionBuilderPage<void>(
key: pageKeyOne,
maintainState: true,
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return const Text('first');
},
),
TransitionBuilderPage<void>(
key: pageKeyTwo,
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return const Text('second');
},
)
];
await tester.pumpWidget(
buildNavigator(
pages: myPages,
onPopPage: (Route<dynamic> route, dynamic result) => null,
transitionDelegate: detector,
)
);
// There should be no transition because the page has the same key.
expect(detector.hasTransition, isFalse);
// Page one sets the maintain state to be true, its widget tree should be
// built.
expect(find.text('first', skipOffstage: false), findsOneWidget);
expect(find.text('second'), findsOneWidget);
});
group('PageRouteBuilder', () { group('PageRouteBuilder', () {
testWidgets('reverseTransitionDuration defaults to 300ms', (WidgetTester tester) async { testWidgets('reverseTransitionDuration defaults to 300ms', (WidgetTester tester) async {
// Default PageRouteBuilder reverse transition duration should be 300ms. // Default PageRouteBuilder reverse transition duration should be 300ms.
......
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