Unverified Commit f86d1089 authored by chunhtai's avatar chunhtai Committed by GitHub

Adds RouterConfig to simply API (#102786)

parent 02b300d9
......@@ -188,15 +188,19 @@ class CupertinoApp extends StatefulWidget {
routeInformationProvider = null,
routeInformationParser = null,
routerDelegate = null,
backButtonDispatcher = null;
backButtonDispatcher = null,
routerConfig = null;
/// Creates a [CupertinoApp] that uses the [Router] instead of a [Navigator].
///
/// {@macro flutter.widgets.WidgetsApp.router}
const CupertinoApp.router({
super.key,
this.routeInformationProvider,
required RouteInformationParser<Object> this.routeInformationParser,
required RouterDelegate<Object> this.routerDelegate,
this.routeInformationParser,
this.routerDelegate,
this.backButtonDispatcher,
this.routerConfig,
this.theme,
this.builder,
this.title = '',
......@@ -217,7 +221,8 @@ class CupertinoApp extends StatefulWidget {
this.restorationScopeId,
this.scrollBehavior,
this.useInheritedMediaQuery = false,
}) : assert(title != null),
}) : assert(routerDelegate != null || routerConfig != null),
assert(title != null),
assert(showPerformanceOverlay != null),
assert(checkerboardRasterCacheImages != null),
assert(checkerboardOffscreenLayers != null),
......@@ -282,6 +287,9 @@ class CupertinoApp extends StatefulWidget {
/// {@macro flutter.widgets.widgetsApp.backButtonDispatcher}
final BackButtonDispatcher? backButtonDispatcher;
/// {@macro flutter.widgets.widgetsApp.routerConfig}
final RouterConfig<Object>? routerConfig;
/// {@macro flutter.widgets.widgetsApp.builder}
final TransitionBuilder? builder;
......@@ -478,7 +486,7 @@ class CupertinoScrollBehavior extends ScrollBehavior {
class _CupertinoAppState extends State<CupertinoApp> {
late HeroController _heroController;
bool get _usesRouter => widget.routerDelegate != null;
bool get _usesRouter => widget.routerDelegate != null || widget.routerConfig != null;
@override
void initState() {
......@@ -519,8 +527,9 @@ class _CupertinoAppState extends State<CupertinoApp> {
return WidgetsApp.router(
key: GlobalObjectKey(this),
routeInformationProvider: widget.routeInformationProvider,
routeInformationParser: widget.routeInformationParser!,
routerDelegate: widget.routerDelegate!,
routeInformationParser: widget.routeInformationParser,
routerDelegate: widget.routerDelegate,
routerConfig: widget.routerConfig,
backButtonDispatcher: widget.backButtonDispatcher,
builder: widget.builder,
title: widget.title,
......
......@@ -248,15 +248,19 @@ class MaterialApp extends StatefulWidget {
routeInformationProvider = null,
routeInformationParser = null,
routerDelegate = null,
backButtonDispatcher = null;
backButtonDispatcher = null,
routerConfig = null;
/// Creates a [MaterialApp] that uses the [Router] instead of a [Navigator].
///
/// {@macro flutter.widgets.WidgetsApp.router}
const MaterialApp.router({
super.key,
this.scaffoldMessengerKey,
this.routeInformationProvider,
required RouteInformationParser<Object> this.routeInformationParser,
required RouterDelegate<Object> this.routerDelegate,
this.routeInformationParser,
this.routerDelegate,
this.routerConfig,
this.backButtonDispatcher,
this.builder,
this.title = '',
......@@ -283,8 +287,7 @@ class MaterialApp extends StatefulWidget {
this.restorationScopeId,
this.scrollBehavior,
this.useInheritedMediaQuery = false,
}) : assert(routeInformationParser != null),
assert(routerDelegate != null),
}) : assert(routerDelegate != null || routerConfig != null),
assert(title != null),
assert(debugShowMaterialGrid != null),
assert(showPerformanceOverlay != null),
......@@ -353,6 +356,9 @@ class MaterialApp extends StatefulWidget {
/// {@macro flutter.widgets.widgetsApp.backButtonDispatcher}
final BackButtonDispatcher? backButtonDispatcher;
/// {@macro flutter.widgets.widgetsApp.routerConfig}
final RouterConfig<Object>? routerConfig;
/// {@macro flutter.widgets.widgetsApp.builder}
///
/// Material specific features such as [showDialog] and [showMenu], and widgets
......@@ -832,7 +838,7 @@ class MaterialScrollBehavior extends ScrollBehavior {
class _MaterialAppState extends State<MaterialApp> {
late HeroController _heroController;
bool get _usesRouter => widget.routerDelegate != null;
bool get _usesRouter => widget.routerDelegate != null || widget.routerConfig != null;
@override
void initState() {
......@@ -925,8 +931,9 @@ class _MaterialAppState extends State<MaterialApp> {
return WidgetsApp.router(
key: GlobalObjectKey(this),
routeInformationProvider: widget.routeInformationProvider,
routeInformationParser: widget.routeInformationParser!,
routerDelegate: widget.routerDelegate!,
routeInformationParser: widget.routeInformationParser,
routerDelegate: widget.routerDelegate,
routerConfig: widget.routerConfig,
backButtonDispatcher: widget.backButtonDispatcher,
builder: _materialBuilder,
title: widget.title,
......
......@@ -399,15 +399,23 @@ class WidgetsApp extends StatefulWidget {
routeInformationProvider = null,
routeInformationParser = null,
routerDelegate = null,
backButtonDispatcher = null;
backButtonDispatcher = null,
routerConfig = null;
/// Creates a [WidgetsApp] that uses the [Router] instead of a [Navigator].
///
/// {@template flutter.widgets.WidgetsApp.router}
/// If the [routerConfig] is provided, the other router related delegates,
/// [routeInformationParser], [routeInformationProvider], [routerDelegate],
/// and [backButtonDispatcher], must all be null.
/// {@endtemplate}
WidgetsApp.router({
super.key,
this.routeInformationProvider,
required RouteInformationParser<Object> this.routeInformationParser,
required RouterDelegate<Object> this.routerDelegate,
BackButtonDispatcher? backButtonDispatcher,
this.routeInformationParser,
this.routerDelegate,
this.routerConfig,
this.backButtonDispatcher,
this.builder,
this.title = '',
this.onGenerateTitle,
......@@ -429,11 +437,21 @@ class WidgetsApp extends StatefulWidget {
this.actions,
this.restorationScopeId,
this.useInheritedMediaQuery = false,
}) : assert(
routeInformationParser != null &&
routerDelegate != null,
'The routeInformationParser and routerDelegate cannot be null.',
),
}) : assert((){
if (routerConfig != null) {
assert(
(routeInformationProvider ?? routeInformationParser ?? routerDelegate ?? backButtonDispatcher) == null,
'If the routerConfig is provided, all the other router delegates must not be provided',
);
return true;
}
assert(routerDelegate != null, 'Either one of routerDelegate or routerConfig must be provided');
assert(
routeInformationProvider == null || routeInformationParser != null,
'If routeInformationProvider is provided, routeInformationParser must also be provided',
);
return true;
}()),
assert(title != null),
assert(color != null),
assert(supportedLocales != null && supportedLocales.isNotEmpty),
......@@ -444,7 +462,6 @@ class WidgetsApp extends StatefulWidget {
assert(debugShowCheckedModeBanner != null),
assert(debugShowWidgetInspector != null),
navigatorObservers = null,
backButtonDispatcher = backButtonDispatcher ?? RootBackButtonDispatcher(),
navigatorKey = null,
onGenerateRoute = null,
pageRouteBuilder = null,
......@@ -524,7 +541,7 @@ class WidgetsApp extends StatefulWidget {
///
/// See also:
///
/// * [Router.routeInformationParser]: which receives this object when this
/// * [Router.routeInformationParser], which receives this object when this
/// widget builds the [Router].
/// {@endtemplate}
final RouteInformationParser<Object>? routeInformationParser;
......@@ -540,7 +557,7 @@ class WidgetsApp extends StatefulWidget {
///
/// See also:
///
/// * [Router.routerDelegate]: which receives this object when this widget
/// * [Router.routerDelegate], which receives this object when this widget
/// builds the [Router].
/// {@endtemplate}
final RouterDelegate<Object>? routerDelegate;
......@@ -555,7 +572,7 @@ class WidgetsApp extends StatefulWidget {
///
/// See also:
///
/// * [Router.backButtonDispatcher]: which receives this object when this
/// * [Router.backButtonDispatcher], which receives this object when this
/// widget builds the [Router].
/// {@endtemplate}
final BackButtonDispatcher? backButtonDispatcher;
......@@ -573,11 +590,25 @@ class WidgetsApp extends StatefulWidget {
///
/// See also:
///
/// * [Router.routeInformationProvider]: which receives this object when this
/// * [Router.routeInformationProvider], which receives this object when this
/// widget builds the [Router].
/// {@endtemplate}
final RouteInformationProvider? routeInformationProvider;
/// {@template flutter.widgets.widgetsApp.routerConfig}
/// An object to configure the underlying [Router].
///
/// If the [routerConfig] is provided, the other router related delegates,
/// [routeInformationParser], [routeInformationProvider], [routerDelegate],
/// and [backButtonDispatcher], must all be null.
///
/// See also:
///
/// * [Router.withConfig], which receives this object when this
/// widget builds the [Router].
/// {@endtemplate}
final RouterConfig<Object>? routerConfig;
/// {@template flutter.widgets.widgetsApp.home}
/// The widget for the default route of the app ([Navigator.defaultRouteName],
/// which is `/`).
......@@ -1295,42 +1326,53 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
super.dispose();
}
void _clearRouterResource() {
_defaultRouteInformationProvider?.dispose();
_defaultRouteInformationProvider = null;
_defaultBackButtonDispatcher = null;
}
void _clearNavigatorResource() {
_navigator = null;
}
void _updateRouting({WidgetsApp? oldWidget}) {
if (_usesRouter) {
assert(!_usesNavigator);
_navigator = null;
if (oldWidget == null || oldWidget.routeInformationProvider != widget.routeInformationProvider) {
if (_usesRouterWithDelegates) {
assert(!_usesNavigator && !_usesRouterWithConfig);
_clearNavigatorResource();
if (widget.routeInformationProvider == null && widget.routeInformationParser != null) {
_defaultRouteInformationProvider ??= PlatformRouteInformationProvider(
initialRouteInformation: RouteInformation(
location: _initialRouteName,
),
);
} else {
_defaultRouteInformationProvider?.dispose();
_defaultRouteInformationProvider = null;
if (widget.routeInformationProvider == null) {
_defaultRouteInformationProvider = PlatformRouteInformationProvider(
initialRouteInformation: RouteInformation(
location: _initialRouteName,
),
);
}
}
if (widget.backButtonDispatcher == null) {
_defaultBackButtonDispatcher ??= RootBackButtonDispatcher();
}
} else if (_usesNavigator) {
assert(!_usesRouter);
_defaultRouteInformationProvider?.dispose();
_defaultRouteInformationProvider = null;
assert(!_usesRouterWithDelegates && !_usesRouterWithConfig);
_clearRouterResource();
if (_navigator == null || widget.navigatorKey != oldWidget!.navigatorKey) {
_navigator = widget.navigatorKey ?? GlobalObjectKey<NavigatorState>(this);
}
assert(_navigator != null);
} else {
assert(widget.builder != null);
assert(!_usesRouter);
assert(!_usesNavigator);
_navigator = null;
_defaultRouteInformationProvider?.dispose();
_defaultRouteInformationProvider = null;
assert(widget.builder != null || _usesRouterWithConfig);
assert(!_usesRouterWithDelegates && !_usesNavigator);
_clearRouterResource();
_clearNavigatorResource();
}
// If we use a navigator, we have a navigator key.
assert(_usesNavigator == (_navigator != null));
}
bool get _usesRouter => widget.routerDelegate != null;
bool get _usesRouterWithDelegates => widget.routerDelegate != null;
bool get _usesRouterWithConfig => widget.routerConfig != null;
bool get _usesNavigator => widget.home != null
|| (widget.routes?.isNotEmpty ?? false)
|| widget.onGenerateRoute != null
......@@ -1340,6 +1382,8 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
RouteInformationProvider? get _effectiveRouteInformationProvider => widget.routeInformationProvider ?? _defaultRouteInformationProvider;
PlatformRouteInformationProvider? _defaultRouteInformationProvider;
BackButtonDispatcher get _effectiveBackButtonDispatcher => widget.backButtonDispatcher ?? _defaultBackButtonDispatcher!;
RootBackButtonDispatcher? _defaultBackButtonDispatcher;
// NAVIGATOR
......@@ -1409,7 +1453,7 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
assert(mounted);
// The back button dispatcher should handle the pop route if we use a
// router.
if (_usesRouter)
if (_usesRouterWithDelegates)
return false;
final NavigatorState? navigator = _navigator?.currentState;
......@@ -1423,7 +1467,7 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
assert(mounted);
// The route name provider should handle the push route if we uses a
// router.
if (_usesRouter)
if (_usesRouterWithDelegates)
return false;
final NavigatorState? navigator = _navigator?.currentState;
......@@ -1535,14 +1579,13 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
@override
Widget build(BuildContext context) {
Widget? routing;
if (_usesRouter) {
assert(_effectiveRouteInformationProvider != null);
if (_usesRouterWithDelegates) {
routing = Router<Object>(
restorationScopeId: 'router',
routeInformationProvider: _effectiveRouteInformationProvider,
routeInformationParser: widget.routeInformationParser,
routerDelegate: widget.routerDelegate!,
backButtonDispatcher: widget.backButtonDispatcher,
backButtonDispatcher: _effectiveBackButtonDispatcher,
);
} else if (_usesNavigator) {
assert(_navigator != null);
......@@ -1560,6 +1603,11 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
observers: widget.navigatorObservers!,
reportsRouteUpdateToEngine: true,
);
} else if (_usesRouterWithConfig) {
routing = Router<Object>.withConfig(
restorationScopeId: 'router',
config: widget.routerConfig!,
);
}
Widget result;
......
......@@ -73,6 +73,46 @@ class RouteInformation {
final Object? state;
}
/// A convenient bundle to configure a [Router] widget.
///
/// To configure a [Router] widget, one needs to provide several delegates,
/// [RouteInformationProvider], [RouteInformationParser], [RouterDelegate],
/// and [BackButtonDispatcher]. This abstract class provides way to bundle these
/// delegates into a single object to configure a [Router].
///
/// The [routerDelegate] must not be null. The [backButtonDispatcher],
/// [routeInformationProvider], and [routeInformationProvider] are optional.
///
/// The [routeInformationProvider] and [routeInformationParser] must
/// both be provided or not provided.
class RouterConfig<T> {
/// Creates a [RouterConfig].
///
/// The [routerDelegate] must not be null. The [backButtonDispatcher],
/// [routeInformationProvider], and [routeInformationParser] are optional.
///
/// The [routeInformationProvider] and [routeInformationParser] must
/// both be provided or not provided.
const RouterConfig({
this.routeInformationProvider,
this.routeInformationParser,
required this.routerDelegate,
this.backButtonDispatcher,
}) : assert((routeInformationProvider == null) == (routeInformationParser == null));
/// The [RouteInformationProvider] that is used to configure the [Router].
final RouteInformationProvider? routeInformationProvider;
/// The [RouteInformationParser] that is used to configure the [Router].
final RouteInformationParser<T>? routeInformationParser;
/// The [RouterDelegate] that is used to configure the [Router].
final RouterDelegate<T> routerDelegate;
/// The [BackButtonDispatcher] that is used to configure the [Router].
final BackButtonDispatcher? backButtonDispatcher;
}
/// The dispatcher for opening and closing pages of an application.
///
/// This widget listens for routing information from the operating system (e.g.
......@@ -275,8 +315,8 @@ class Router<T> extends StatefulWidget {
/// router does not depend on route information. A common example is a sub router
/// that builds its content completely based on the app state.
///
/// If the [routeInformationProvider] or [restorationScopeId] is not null, then
/// [routeInformationParser] must also not be null.
/// The [routeInformationProvider] and [routeInformationParser] must
/// both be provided or not provided.
///
/// The [routerDelegate] must not be null.
const Router({
......@@ -286,11 +326,38 @@ class Router<T> extends StatefulWidget {
required this.routerDelegate,
this.backButtonDispatcher,
this.restorationScopeId,
}) : assert(
(routeInformationProvider == null && restorationScopeId == null) || routeInformationParser != null,
'A routeInformationParser must be provided when a routeInformationProvider or a restorationId is specified.'
),
assert(routerDelegate != null);
}) : assert(
routeInformationProvider == null || routeInformationParser != null,
'A routeInformationParser must be provided when a routeInformationProvider is specified.',
),
assert(routerDelegate != null);
/// Creates a router with a [RouterConfig].
///
/// The [RouterConfig.routeInformationProvider] and
/// [RouterConfig.routeInformationParser] can be null if this router does not
/// depend on route information. A common example is a sub router that builds
/// its content completely based on the app state.
///
/// If the [RouterConfig.routeInformationProvider] is not null, then
/// [RouterConfig.routeInformationParser] must also not be
/// null.
///
/// The [RouterConfig.routerDelegate] must not be null.
factory Router.withConfig({
Key? key,
required RouterConfig<T> config,
String? restorationScopeId,
}) {
return Router<T>(
key: key,
routeInformationProvider: config.routeInformationProvider,
routeInformationParser: config.routeInformationParser,
routerDelegate: config.routerDelegate,
backButtonDispatcher: config.backButtonDispatcher,
restorationScopeId: restorationScopeId,
);
}
/// The route information provider for the router.
///
......@@ -299,8 +366,8 @@ class Router<T> extends StatefulWidget {
/// it notifies.
///
/// This can be null if this router does not rely on the route information
/// to build its content. In such case, the [routeInformationParser] can also be
/// null.
/// to build its content. In such case, the [routeInformationParser] must also
/// be null.
final RouteInformationProvider? routeInformationProvider;
/// The route information parser for the router.
......@@ -511,6 +578,7 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin {
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
registerForRestoration(_routeInformation, 'route');
if (_routeInformation.value != null) {
assert(widget.routeInformationParser != null);
_processRouteInformation(_routeInformation.value!, () => widget.routerDelegate.setRestoredRoutePath);
} else if (widget.routeInformationProvider != null) {
_processRouteInformation(widget.routeInformationProvider!.value, () => widget.routerDelegate.setInitialRoutePath);
......
......@@ -176,6 +176,110 @@ void main() {
expect(find.text('popped'), findsOneWidget);
});
testWidgets('CupertinoApp.router route information parser is optional', (WidgetTester tester) async {
final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate(
builder: (BuildContext context, RouteInformation information) {
return Text(information.location!);
},
onPopPage: (Route<void> route, void result, SimpleNavigatorRouterDelegate delegate) {
delegate.routeInformation = const RouteInformation(
location: 'popped',
);
return route.didPop(result);
},
);
delegate.routeInformation = const RouteInformation(location: 'initial');
await tester.pumpWidget(CupertinoApp.router(
routerDelegate: delegate,
));
expect(find.text('initial'), findsOneWidget);
// Simulate android back button intent.
final ByteData message = const JSONMethodCodec().encodeMethodCall(const MethodCall('popRoute'));
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
await tester.pumpAndSettle();
expect(find.text('popped'), findsOneWidget);
});
testWidgets('CupertinoApp.router throw if route information provider is provided but no route information parser', (WidgetTester tester) async {
final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate(
builder: (BuildContext context, RouteInformation information) {
return Text(information.location!);
},
onPopPage: (Route<void> route, void result, SimpleNavigatorRouterDelegate delegate) {
delegate.routeInformation = const RouteInformation(
location: 'popped',
);
return route.didPop(result);
},
);
delegate.routeInformation = const RouteInformation(location: 'initial');
final PlatformRouteInformationProvider provider = PlatformRouteInformationProvider(
initialRouteInformation: const RouteInformation(
location: 'initial',
),
);
await tester.pumpWidget(CupertinoApp.router(
routeInformationProvider: provider,
routerDelegate: delegate,
));
expect(tester.takeException(), isAssertionError);
});
testWidgets('CupertinoApp.router throw if route configuration is provided along with other delegate', (WidgetTester tester) async {
final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate(
builder: (BuildContext context, RouteInformation information) {
return Text(information.location!);
},
onPopPage: (Route<void> route, void result, SimpleNavigatorRouterDelegate delegate) {
delegate.routeInformation = const RouteInformation(
location: 'popped',
);
return route.didPop(result);
},
);
delegate.routeInformation = const RouteInformation(location: 'initial');
final RouterConfig<RouteInformation> routerConfig = RouterConfig<RouteInformation>(routerDelegate: delegate);
await tester.pumpWidget(CupertinoApp.router(
routerDelegate: delegate,
routerConfig: routerConfig,
));
expect(tester.takeException(), isAssertionError);
});
testWidgets('CupertinoApp.router router config works', (WidgetTester tester) async {
final RouterConfig<RouteInformation> routerConfig = RouterConfig<RouteInformation>(
routeInformationProvider: PlatformRouteInformationProvider(
initialRouteInformation: const RouteInformation(
location: 'initial',
),
),
routeInformationParser: SimpleRouteInformationParser(),
routerDelegate: SimpleNavigatorRouterDelegate(
builder: (BuildContext context, RouteInformation information) {
return Text(information.location!);
},
onPopPage: (Route<void> route, void result, SimpleNavigatorRouterDelegate delegate) {
delegate.routeInformation = const RouteInformation(
location: 'popped',
);
return route.didPop(result);
},
),
backButtonDispatcher: RootBackButtonDispatcher()
);
await tester.pumpWidget(CupertinoApp.router(
routerConfig: routerConfig,
));
expect(find.text('initial'), findsOneWidget);
// Simulate android back button intent.
final ByteData message = const JSONMethodCodec().encodeMethodCall(const MethodCall('popRoute'));
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
await tester.pumpAndSettle();
expect(find.text('popped'), findsOneWidget);
});
testWidgets('CupertinoApp has correct default ScrollBehavior', (WidgetTester tester) async {
late BuildContext capturedContext;
await tester.pumpWidget(
......
......@@ -1023,6 +1023,110 @@ void main() {
expect(find.text('popped'), findsOneWidget);
});
testWidgets('MaterialApp.router route information parser is optional', (WidgetTester tester) async {
final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate(
builder: (BuildContext context, RouteInformation information) {
return Text(information.location!);
},
onPopPage: (Route<void> route, void result, SimpleNavigatorRouterDelegate delegate) {
delegate.routeInformation = const RouteInformation(
location: 'popped',
);
return route.didPop(result);
},
);
delegate.routeInformation = const RouteInformation(location: 'initial');
await tester.pumpWidget(MaterialApp.router(
routerDelegate: delegate,
));
expect(find.text('initial'), findsOneWidget);
// Simulate android back button intent.
final ByteData message = const JSONMethodCodec().encodeMethodCall(const MethodCall('popRoute'));
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
await tester.pumpAndSettle();
expect(find.text('popped'), findsOneWidget);
});
testWidgets('MaterialApp.router throw if route information provider is provided but no route information parser', (WidgetTester tester) async {
final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate(
builder: (BuildContext context, RouteInformation information) {
return Text(information.location!);
},
onPopPage: (Route<void> route, void result, SimpleNavigatorRouterDelegate delegate) {
delegate.routeInformation = const RouteInformation(
location: 'popped',
);
return route.didPop(result);
},
);
delegate.routeInformation = const RouteInformation(location: 'initial');
final PlatformRouteInformationProvider provider = PlatformRouteInformationProvider(
initialRouteInformation: const RouteInformation(
location: 'initial',
),
);
await tester.pumpWidget(MaterialApp.router(
routeInformationProvider: provider,
routerDelegate: delegate,
));
expect(tester.takeException(), isAssertionError);
});
testWidgets('MaterialApp.router throw if route configuration is provided along with other delegate', (WidgetTester tester) async {
final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate(
builder: (BuildContext context, RouteInformation information) {
return Text(information.location!);
},
onPopPage: (Route<void> route, void result, SimpleNavigatorRouterDelegate delegate) {
delegate.routeInformation = const RouteInformation(
location: 'popped',
);
return route.didPop(result);
},
);
delegate.routeInformation = const RouteInformation(location: 'initial');
final RouterConfig<RouteInformation> routerConfig = RouterConfig<RouteInformation>(routerDelegate: delegate);
await tester.pumpWidget(MaterialApp.router(
routerDelegate: delegate,
routerConfig: routerConfig,
));
expect(tester.takeException(), isAssertionError);
});
testWidgets('MaterialApp.router router config works', (WidgetTester tester) async {
final RouterConfig<RouteInformation> routerConfig = RouterConfig<RouteInformation>(
routeInformationProvider: PlatformRouteInformationProvider(
initialRouteInformation: const RouteInformation(
location: 'initial',
),
),
routeInformationParser: SimpleRouteInformationParser(),
routerDelegate: SimpleNavigatorRouterDelegate(
builder: (BuildContext context, RouteInformation information) {
return Text(information.location!);
},
onPopPage: (Route<void> route, void result, SimpleNavigatorRouterDelegate delegate) {
delegate.routeInformation = const RouteInformation(
location: 'popped',
);
return route.didPop(result);
},
),
backButtonDispatcher: RootBackButtonDispatcher()
);
await tester.pumpWidget(MaterialApp.router(
routerConfig: routerConfig,
));
expect(find.text('initial'), findsOneWidget);
// Simulate android back button intent.
final ByteData message = const JSONMethodCodec().encodeMethodCall(const MethodCall('popRoute'));
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
await tester.pumpAndSettle();
expect(find.text('popped'), findsOneWidget);
});
testWidgets('MaterialApp.builder can build app without a Navigator', (WidgetTester tester) async {
Widget? builderChild;
await tester.pumpWidget(MaterialApp(
......
......@@ -305,6 +305,116 @@ void main() {
expect(find.text('popped'), findsOneWidget);
});
testWidgets('WidgetsApp.router route information parser is optional', (WidgetTester tester) async {
final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate(
builder: (BuildContext context, RouteInformation information) {
return Text(information.location!);
},
onPopPage: (Route<void> route, void result, SimpleNavigatorRouterDelegate delegate) {
delegate.routeInformation = const RouteInformation(
location: 'popped',
);
return route.didPop(result);
},
);
delegate.routeInformation = const RouteInformation(location: 'initial');
await tester.pumpWidget(WidgetsApp.router(
routerDelegate: delegate,
color: const Color(0xFF123456),
));
expect(find.text('initial'), findsOneWidget);
// Simulate android back button intent.
final ByteData message = const JSONMethodCodec().encodeMethodCall(const MethodCall('popRoute'));
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
await tester.pumpAndSettle();
expect(find.text('popped'), findsOneWidget);
});
testWidgets('WidgetsApp.router throw if route information provider is provided but no route information parser', (WidgetTester tester) async {
final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate(
builder: (BuildContext context, RouteInformation information) {
return Text(information.location!);
},
onPopPage: (Route<void> route, void result, SimpleNavigatorRouterDelegate delegate) {
delegate.routeInformation = const RouteInformation(
location: 'popped',
);
return route.didPop(result);
},
);
delegate.routeInformation = const RouteInformation(location: 'initial');
final PlatformRouteInformationProvider provider = PlatformRouteInformationProvider(
initialRouteInformation: const RouteInformation(
location: 'initial',
),
);
await expectLater(() async {
await tester.pumpWidget(WidgetsApp.router(
routeInformationProvider: provider,
routerDelegate: delegate,
color: const Color(0xFF123456),
));
}, throwsAssertionError);
});
testWidgets('WidgetsApp.router throw if route configuration is provided along with other delegate', (WidgetTester tester) async {
final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate(
builder: (BuildContext context, RouteInformation information) {
return Text(information.location!);
},
onPopPage: (Route<void> route, void result, SimpleNavigatorRouterDelegate delegate) {
delegate.routeInformation = const RouteInformation(
location: 'popped',
);
return route.didPop(result);
},
);
delegate.routeInformation = const RouteInformation(location: 'initial');
final RouterConfig<RouteInformation> routerConfig = RouterConfig<RouteInformation>(routerDelegate: delegate);
await expectLater(() async {
await tester.pumpWidget(WidgetsApp.router(
routerDelegate: delegate,
routerConfig: routerConfig,
color: const Color(0xFF123456),
));
}, throwsAssertionError);
});
testWidgets('WidgetsApp.router router config works', (WidgetTester tester) async {
final RouterConfig<RouteInformation> routerConfig = RouterConfig<RouteInformation>(
routeInformationProvider: PlatformRouteInformationProvider(
initialRouteInformation: const RouteInformation(
location: 'initial',
),
),
routeInformationParser: SimpleRouteInformationParser(),
routerDelegate: SimpleNavigatorRouterDelegate(
builder: (BuildContext context, RouteInformation information) {
return Text(information.location!);
},
onPopPage: (Route<void> route, void result, SimpleNavigatorRouterDelegate delegate) {
delegate.routeInformation = const RouteInformation(
location: 'popped',
);
return route.didPop(result);
},
),
backButtonDispatcher: RootBackButtonDispatcher()
);
await tester.pumpWidget(WidgetsApp.router(
routerConfig: routerConfig,
color: const Color(0xFF123456),
));
expect(find.text('initial'), findsOneWidget);
// Simulate android back button intent.
final ByteData message = const JSONMethodCodec().encodeMethodCall(const MethodCall('popRoute'));
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
await tester.pumpAndSettle();
expect(find.text('popped'), findsOneWidget);
});
testWidgets('WidgetsApp.router has correct default', (WidgetTester tester) async {
final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate(
builder: (BuildContext context, RouteInformation information) {
......
......@@ -202,27 +202,7 @@ void main() {
throwsA(isAssertionError.having(
(AssertionError e) => e.message,
'message',
'A routeInformationParser must be provided when a routeInformationProvider or a restorationId is specified.',
)),
);
});
testWidgets('Router throw when passing restorationId without routeInformationParser', (WidgetTester tester) async {
expect(
() {
Router<RouteInformation>(
restorationScopeId: 'foo',
routerDelegate: SimpleRouterDelegate(
builder: (BuildContext context, RouteInformation? information) {
return Text(information!.location!);
},
),
);
},
throwsA(isAssertionError.having(
(AssertionError e) => e.message,
'message',
'A routeInformationParser must be provided when a routeInformationProvider or a restorationId is specified.',
'A routeInformationParser must be provided when a routeInformationProvider is specified.',
)),
);
});
......@@ -1470,6 +1450,27 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester
expect(find.text('$expectedMaxLines'), findsOneWidget);
expect(parserCalled, isFalse);
});
testWidgets('Router can initialize with RouterConfig', (WidgetTester tester) async {
const String expected = 'text';
final RouterConfig<RouteInformation> config = RouterConfig<RouteInformation>(
routeInformationProvider: SimpleRouteInformationProvider()..value = const RouteInformation(location: '/'),
routeInformationParser: SimpleRouteInformationParser(),
routerDelegate: SimpleRouterDelegate(
builder: (_, __) => const Text(expected),
),
backButtonDispatcher: RootBackButtonDispatcher(),
);
final Router<RouteInformation> router = Router<RouteInformation>.withConfig(config: config);
expect(router.routerDelegate, config.routerDelegate);
expect(router.routeInformationParser, config.routeInformationParser);
expect(router.routeInformationProvider, config.routeInformationProvider);
expect(router.backButtonDispatcher, config.backButtonDispatcher);
await tester.pumpWidget(buildBoilerPlate(router));
expect(find.text(expected), findsOneWidget);
});
}
Widget buildBoilerPlate(Widget child) {
......
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