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;
|
||||
RouteInformationReportingType? _currentIntentionToReport;
|
||||
final _RestorableRouteInformation _routeInformation = _RestorableRouteInformation();
|
||||
late bool _routeParsePending;
|
||||
|
||||
@override
|
||||
String? get restorationId => widget.restorationScopeId;
|
||||
@ -580,7 +581,15 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin {
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
_routeParsePending = true;
|
||||
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();
|
||||
}
|
||||
|
||||
@ -621,9 +630,11 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin {
|
||||
}
|
||||
|
||||
void _processRouteInformation(RouteInformation information, ValueGetter<_RouteSetter<T>> delegateRouteSetter) {
|
||||
assert(_routeParsePending);
|
||||
_routeParsePending = false;
|
||||
_currentRouterTransaction = Object();
|
||||
widget.routeInformationParser!
|
||||
.parseRouteInformation(information)
|
||||
.parseRouteInformationWithDependencies(information, context)
|
||||
.then<void>(_processParsedRouteInformation(_currentRouterTransaction, delegateRouteSetter));
|
||||
}
|
||||
|
||||
@ -641,6 +652,7 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin {
|
||||
|
||||
void _handleRouteInformationProviderNotification() {
|
||||
assert(widget.routeInformationProvider!.value != null);
|
||||
_routeParsePending = true;
|
||||
_processRouteInformation(widget.routeInformationProvider!.value, () => widget.routerDelegate.setNewRoutePath);
|
||||
}
|
||||
|
||||
@ -685,8 +697,10 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin {
|
||||
routerDelegate: widget.routerDelegate,
|
||||
routerState: this,
|
||||
child: Builder(
|
||||
// We use a Builder so that the build method below
|
||||
// will have a BuildContext that contains the _RouterScope.
|
||||
// Use a Builder so that the build method below will have a
|
||||
// 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,
|
||||
),
|
||||
),
|
||||
@ -1079,11 +1093,16 @@ class _BackButtonListenerState extends State<BackButtonListener> {
|
||||
/// route information from [Router.routeInformationProvider] and any subsequent
|
||||
/// new route notifications from it. The [Router] widget calls the [parseRouteInformation]
|
||||
/// 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 const constructor. This constructor enables subclasses to provide
|
||||
/// const constructors so that they can be used in const expressions.
|
||||
const RouteInformationParser();
|
||||
|
||||
/// {@template flutter.widgets.RouteInformationParser.parseRouteInformation}
|
||||
/// Converts the given route information into parsed data to pass to a
|
||||
/// [RouterDelegate].
|
||||
///
|
||||
@ -1094,7 +1113,30 @@ abstract class RouteInformationParser<T> {
|
||||
/// Consider using a [SynchronousFuture] if the result can be computed
|
||||
/// synchronously, so that the [Router] does not need to wait for the next
|
||||
/// 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.
|
||||
///
|
||||
|
@ -1308,6 +1308,168 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester
|
||||
expect(find.text('Current route: /404'), findsOneWidget);
|
||||
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) {
|
||||
@ -1322,6 +1484,7 @@ typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInforma
|
||||
typedef SimpleRouterDelegatePopRoute = Future<bool> Function();
|
||||
typedef SimpleNavigatorRouterDelegatePopPage<T> = bool Function(Route<T> route, T result);
|
||||
typedef RouterReportRouterInformation = void Function(RouteInformation, RouteInformationReportingType);
|
||||
typedef CustomRouteInformationParserCallback = RouteInformation Function(RouteInformation, BuildContext);
|
||||
|
||||
class SimpleRouteInformationParser extends RouteInformationParser<RouteInformation> {
|
||||
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 {
|
||||
SimpleRouterDelegate({
|
||||
this.builder,
|
||||
|
Loading…
Reference in New Issue
Block a user