Unverified Commit 5b713147 authored by chunhtai's avatar chunhtai Committed by GitHub

Add RouteInformationParser.parseRouteInformationWithDependencies (#102414)

parent e4bd552e
...@@ -494,6 +494,7 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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> { ...@@ -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> { ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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,
......
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