mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Add RouteInformationParser.parseRouteInformationWithDependencies (#102414)
This commit is contained in:
parent
e4bd552e59
commit
5b71314740
@ -494,6 +494,7 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin {
|
|||||||
Object? _currentRouterTransaction;
|
Object? _currentRouterTransaction;
|
||||||
RouteInformationReportingType? _currentIntentionToReport;
|
RouteInformationReportingType? _currentIntentionToReport;
|
||||||
final _RestorableRouteInformation _routeInformation = _RestorableRouteInformation();
|
final _RestorableRouteInformation _routeInformation = _RestorableRouteInformation();
|
||||||
|
late bool _routeParsePending;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String? get restorationId => widget.restorationScopeId;
|
String? get restorationId => widget.restorationScopeId;
|
||||||
@ -580,7 +581,15 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
|
_routeParsePending = true;
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
|
// The super.didChangeDependencies may have parsed the route information.
|
||||||
|
// This can happen if the didChangeDependencies is triggered by state
|
||||||
|
// restoration or first build.
|
||||||
|
if (widget.routeInformationProvider != null && _routeParsePending) {
|
||||||
|
_processRouteInformation(widget.routeInformationProvider!.value, () => widget.routerDelegate.setNewRoutePath);
|
||||||
|
}
|
||||||
|
_routeParsePending = false;
|
||||||
_maybeNeedToReportRouteInformation();
|
_maybeNeedToReportRouteInformation();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -621,9 +630,11 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _processRouteInformation(RouteInformation information, ValueGetter<_RouteSetter<T>> delegateRouteSetter) {
|
void _processRouteInformation(RouteInformation information, ValueGetter<_RouteSetter<T>> delegateRouteSetter) {
|
||||||
|
assert(_routeParsePending);
|
||||||
|
_routeParsePending = false;
|
||||||
_currentRouterTransaction = Object();
|
_currentRouterTransaction = Object();
|
||||||
widget.routeInformationParser!
|
widget.routeInformationParser!
|
||||||
.parseRouteInformation(information)
|
.parseRouteInformationWithDependencies(information, context)
|
||||||
.then<void>(_processParsedRouteInformation(_currentRouterTransaction, delegateRouteSetter));
|
.then<void>(_processParsedRouteInformation(_currentRouterTransaction, delegateRouteSetter));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -641,6 +652,7 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin {
|
|||||||
|
|
||||||
void _handleRouteInformationProviderNotification() {
|
void _handleRouteInformationProviderNotification() {
|
||||||
assert(widget.routeInformationProvider!.value != null);
|
assert(widget.routeInformationProvider!.value != null);
|
||||||
|
_routeParsePending = true;
|
||||||
_processRouteInformation(widget.routeInformationProvider!.value, () => widget.routerDelegate.setNewRoutePath);
|
_processRouteInformation(widget.routeInformationProvider!.value, () => widget.routerDelegate.setNewRoutePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -685,8 +697,10 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin {
|
|||||||
routerDelegate: widget.routerDelegate,
|
routerDelegate: widget.routerDelegate,
|
||||||
routerState: this,
|
routerState: this,
|
||||||
child: Builder(
|
child: Builder(
|
||||||
// We use a Builder so that the build method below
|
// Use a Builder so that the build method below will have a
|
||||||
// will have a BuildContext that contains the _RouterScope.
|
// BuildContext that contains the _RouterScope. This also prevents
|
||||||
|
// dependencies look ups in routerDelegate from rebuilding Router
|
||||||
|
// widget that may result in re-parsing the route information.
|
||||||
builder: widget.routerDelegate.build,
|
builder: widget.routerDelegate.build,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -1079,11 +1093,16 @@ class _BackButtonListenerState extends State<BackButtonListener> {
|
|||||||
/// route information from [Router.routeInformationProvider] and any subsequent
|
/// route information from [Router.routeInformationProvider] and any subsequent
|
||||||
/// new route notifications from it. The [Router] widget calls the [parseRouteInformation]
|
/// new route notifications from it. The [Router] widget calls the [parseRouteInformation]
|
||||||
/// with the route information from [Router.routeInformationProvider].
|
/// with the route information from [Router.routeInformationProvider].
|
||||||
|
///
|
||||||
|
/// One of the [parseRouteInformation] or
|
||||||
|
/// [parseRouteInformationWithDependencies] must be implemented, otherwise a
|
||||||
|
/// runtime error will be thrown.
|
||||||
abstract class RouteInformationParser<T> {
|
abstract class RouteInformationParser<T> {
|
||||||
/// Abstract const constructor. This constructor enables subclasses to provide
|
/// Abstract const constructor. This constructor enables subclasses to provide
|
||||||
/// const constructors so that they can be used in const expressions.
|
/// const constructors so that they can be used in const expressions.
|
||||||
const RouteInformationParser();
|
const RouteInformationParser();
|
||||||
|
|
||||||
|
/// {@template flutter.widgets.RouteInformationParser.parseRouteInformation}
|
||||||
/// Converts the given route information into parsed data to pass to a
|
/// Converts the given route information into parsed data to pass to a
|
||||||
/// [RouterDelegate].
|
/// [RouterDelegate].
|
||||||
///
|
///
|
||||||
@ -1094,7 +1113,30 @@ abstract class RouteInformationParser<T> {
|
|||||||
/// Consider using a [SynchronousFuture] if the result can be computed
|
/// Consider using a [SynchronousFuture] if the result can be computed
|
||||||
/// synchronously, so that the [Router] does not need to wait for the next
|
/// synchronously, so that the [Router] does not need to wait for the next
|
||||||
/// microtask to pass the data to the [RouterDelegate].
|
/// microtask to pass the data to the [RouterDelegate].
|
||||||
Future<T> parseRouteInformation(RouteInformation routeInformation);
|
/// {@endtemplate}
|
||||||
|
///
|
||||||
|
/// One can implement [parseRouteInformationWithDependencies] instead if
|
||||||
|
/// the parsing depends on other dependencies from the [BuildContext].
|
||||||
|
Future<T> parseRouteInformation(RouteInformation routeInformation) {
|
||||||
|
throw UnimplementedError(
|
||||||
|
'One of the parseRouteInformation or '
|
||||||
|
'parseRouteInformationWithDependencies must be implemented'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@macro flutter.widgets.RouteInformationParser.parseRouteInformation}
|
||||||
|
///
|
||||||
|
/// The input [BuildContext] can be used for looking up [InheritedWidget]s
|
||||||
|
/// If one uses [BuildContext.dependOnInheritedWidgetOfExactType], a
|
||||||
|
/// dependency will be created. The [Router] will reparse the
|
||||||
|
/// [RouteInformation] from its [RouteInformationProvider] if the dependency
|
||||||
|
/// notifies its listeners.
|
||||||
|
///
|
||||||
|
/// One can also use [BuildContext.getElementForInheritedWidgetOfExactType] to
|
||||||
|
/// look up [InheritedWidget]s without creating dependencies.
|
||||||
|
Future<T> parseRouteInformationWithDependencies(RouteInformation routeInformation, BuildContext context) {
|
||||||
|
return parseRouteInformation(routeInformation);
|
||||||
|
}
|
||||||
|
|
||||||
/// Restore the route information from the given configuration.
|
/// Restore the route information from the given configuration.
|
||||||
///
|
///
|
||||||
|
@ -1308,6 +1308,168 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester
|
|||||||
expect(find.text('Current route: /404'), findsOneWidget);
|
expect(find.text('Current route: /404'), findsOneWidget);
|
||||||
expect(reportedRouteInformation[1].location, '/404');
|
expect(reportedRouteInformation[1].location, '/404');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('RouterInformationParser can look up dependencies and reparse', (WidgetTester tester) async {
|
||||||
|
final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider();
|
||||||
|
provider.value = const RouteInformation(
|
||||||
|
location: 'initial',
|
||||||
|
);
|
||||||
|
final BackButtonDispatcher dispatcher = RootBackButtonDispatcher();
|
||||||
|
int expectedMaxLines = 1;
|
||||||
|
bool parserCalled = false;
|
||||||
|
final Widget router = Router<RouteInformation>(
|
||||||
|
routeInformationProvider: provider,
|
||||||
|
routeInformationParser: CustomRouteInformationParser((RouteInformation information, BuildContext context) {
|
||||||
|
parserCalled = true;
|
||||||
|
final DefaultTextStyle style = DefaultTextStyle.of(context);
|
||||||
|
return RouteInformation(location: '${style.maxLines}');
|
||||||
|
}),
|
||||||
|
routerDelegate: SimpleRouterDelegate(
|
||||||
|
builder: (BuildContext context, RouteInformation? information) {
|
||||||
|
return Text(information!.location!);
|
||||||
|
},
|
||||||
|
onPopRoute: () {
|
||||||
|
provider.value = const RouteInformation(
|
||||||
|
location: 'popped',
|
||||||
|
);
|
||||||
|
return SynchronousFuture<bool>(true);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
backButtonDispatcher: dispatcher,
|
||||||
|
);
|
||||||
|
await tester.pumpWidget(buildBoilerPlate(
|
||||||
|
DefaultTextStyle(
|
||||||
|
style: const TextStyle(),
|
||||||
|
maxLines: expectedMaxLines,
|
||||||
|
child: router,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(find.text('$expectedMaxLines'), findsOneWidget);
|
||||||
|
expect(parserCalled, isTrue);
|
||||||
|
|
||||||
|
parserCalled = false;
|
||||||
|
expectedMaxLines = 2;
|
||||||
|
await tester.pumpWidget(buildBoilerPlate(
|
||||||
|
DefaultTextStyle(
|
||||||
|
style: const TextStyle(),
|
||||||
|
maxLines: expectedMaxLines,
|
||||||
|
child: router,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
await tester.pump();
|
||||||
|
expect(find.text('$expectedMaxLines'), findsOneWidget);
|
||||||
|
expect(parserCalled, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('RouterInformationParser can look up dependencies without reparsing', (WidgetTester tester) async {
|
||||||
|
final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider();
|
||||||
|
provider.value = const RouteInformation(
|
||||||
|
location: 'initial',
|
||||||
|
);
|
||||||
|
final BackButtonDispatcher dispatcher = RootBackButtonDispatcher();
|
||||||
|
const int expectedMaxLines = 1;
|
||||||
|
bool parserCalled = false;
|
||||||
|
final Widget router = Router<RouteInformation>(
|
||||||
|
routeInformationProvider: provider,
|
||||||
|
routeInformationParser: CustomRouteInformationParser((RouteInformation information, BuildContext context) {
|
||||||
|
parserCalled = true;
|
||||||
|
final DefaultTextStyle style = context.getElementForInheritedWidgetOfExactType<DefaultTextStyle>()!.widget as DefaultTextStyle;
|
||||||
|
return RouteInformation(location: '${style.maxLines}');
|
||||||
|
}),
|
||||||
|
routerDelegate: SimpleRouterDelegate(
|
||||||
|
builder: (BuildContext context, RouteInformation? information) {
|
||||||
|
return Text(information!.location!);
|
||||||
|
},
|
||||||
|
onPopRoute: () {
|
||||||
|
provider.value = const RouteInformation(
|
||||||
|
location: 'popped',
|
||||||
|
);
|
||||||
|
return SynchronousFuture<bool>(true);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
backButtonDispatcher: dispatcher,
|
||||||
|
);
|
||||||
|
await tester.pumpWidget(buildBoilerPlate(
|
||||||
|
DefaultTextStyle(
|
||||||
|
style: const TextStyle(),
|
||||||
|
maxLines: expectedMaxLines,
|
||||||
|
child: router,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(find.text('$expectedMaxLines'), findsOneWidget);
|
||||||
|
expect(parserCalled, isTrue);
|
||||||
|
|
||||||
|
parserCalled = false;
|
||||||
|
const int newMaxLines = 2;
|
||||||
|
// This rebuild should not trigger re-parsing.
|
||||||
|
await tester.pumpWidget(buildBoilerPlate(
|
||||||
|
DefaultTextStyle(
|
||||||
|
style: const TextStyle(),
|
||||||
|
maxLines: newMaxLines,
|
||||||
|
child: router,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
await tester.pump();
|
||||||
|
expect(find.text('$newMaxLines'), findsNothing);
|
||||||
|
expect(find.text('$expectedMaxLines'), findsOneWidget);
|
||||||
|
expect(parserCalled, isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Looks up dependencies in RouterDelegate does not trigger re-parsing', (WidgetTester tester) async {
|
||||||
|
final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider();
|
||||||
|
provider.value = const RouteInformation(
|
||||||
|
location: 'initial',
|
||||||
|
);
|
||||||
|
final BackButtonDispatcher dispatcher = RootBackButtonDispatcher();
|
||||||
|
int expectedMaxLines = 1;
|
||||||
|
bool parserCalled = false;
|
||||||
|
final Widget router = Router<RouteInformation>(
|
||||||
|
routeInformationProvider: provider,
|
||||||
|
routeInformationParser: CustomRouteInformationParser((RouteInformation information, BuildContext context) {
|
||||||
|
parserCalled = true;
|
||||||
|
return information;
|
||||||
|
}),
|
||||||
|
routerDelegate: SimpleRouterDelegate(
|
||||||
|
builder: (BuildContext context, RouteInformation? information) {
|
||||||
|
final DefaultTextStyle style = DefaultTextStyle.of(context);
|
||||||
|
return Text('${style.maxLines}');
|
||||||
|
},
|
||||||
|
onPopRoute: () {
|
||||||
|
provider.value = const RouteInformation(
|
||||||
|
location: 'popped',
|
||||||
|
);
|
||||||
|
return SynchronousFuture<bool>(true);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
backButtonDispatcher: dispatcher,
|
||||||
|
);
|
||||||
|
await tester.pumpWidget(buildBoilerPlate(
|
||||||
|
DefaultTextStyle(
|
||||||
|
style: const TextStyle(),
|
||||||
|
maxLines: expectedMaxLines,
|
||||||
|
child: router,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(find.text('$expectedMaxLines'), findsOneWidget);
|
||||||
|
// Initial route will be parsed regardless.
|
||||||
|
expect(parserCalled, isTrue);
|
||||||
|
|
||||||
|
parserCalled = false;
|
||||||
|
expectedMaxLines = 2;
|
||||||
|
await tester.pumpWidget(buildBoilerPlate(
|
||||||
|
DefaultTextStyle(
|
||||||
|
style: const TextStyle(),
|
||||||
|
maxLines: expectedMaxLines,
|
||||||
|
child: router,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
await tester.pump();
|
||||||
|
expect(find.text('$expectedMaxLines'), findsOneWidget);
|
||||||
|
expect(parserCalled, isFalse);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildBoilerPlate(Widget child) {
|
Widget buildBoilerPlate(Widget child) {
|
||||||
@ -1322,6 +1484,7 @@ typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInforma
|
|||||||
typedef SimpleRouterDelegatePopRoute = Future<bool> Function();
|
typedef SimpleRouterDelegatePopRoute = Future<bool> Function();
|
||||||
typedef SimpleNavigatorRouterDelegatePopPage<T> = bool Function(Route<T> route, T result);
|
typedef SimpleNavigatorRouterDelegatePopPage<T> = bool Function(Route<T> route, T result);
|
||||||
typedef RouterReportRouterInformation = void Function(RouteInformation, RouteInformationReportingType);
|
typedef RouterReportRouterInformation = void Function(RouteInformation, RouteInformationReportingType);
|
||||||
|
typedef CustomRouteInformationParserCallback = RouteInformation Function(RouteInformation, BuildContext);
|
||||||
|
|
||||||
class SimpleRouteInformationParser extends RouteInformationParser<RouteInformation> {
|
class SimpleRouteInformationParser extends RouteInformationParser<RouteInformation> {
|
||||||
SimpleRouteInformationParser();
|
SimpleRouteInformationParser();
|
||||||
@ -1337,6 +1500,22 @@ class SimpleRouteInformationParser extends RouteInformationParser<RouteInformati
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CustomRouteInformationParser extends RouteInformationParser<RouteInformation> {
|
||||||
|
const CustomRouteInformationParser(this.callback);
|
||||||
|
|
||||||
|
final CustomRouteInformationParserCallback callback;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<RouteInformation> parseRouteInformationWithDependencies(RouteInformation information, BuildContext context) {
|
||||||
|
return SynchronousFuture<RouteInformation>(callback(information, context));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
RouteInformation restoreRouteInformation(RouteInformation configuration) {
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SimpleRouterDelegate extends RouterDelegate<RouteInformation> with ChangeNotifier {
|
class SimpleRouterDelegate extends RouterDelegate<RouteInformation> with ChangeNotifier {
|
||||||
SimpleRouterDelegate({
|
SimpleRouterDelegate({
|
||||||
this.builder,
|
this.builder,
|
||||||
|
Loading…
Reference in New Issue
Block a user