Skip to content

Commit

Permalink
Implement url support for RouteInformation and didPushRouteInformation (
Browse files Browse the repository at this point in the history
#119968)

Related #100624

The goal is to make sure the engine can send a location string in either the existing format or a complete uri string to the framework, and the framework will still work as usual.
  • Loading branch information
chunhtai committed Apr 24, 2023
1 parent 034adb6 commit 3b4ac4d
Show file tree
Hide file tree
Showing 13 changed files with 466 additions and 315 deletions.
11 changes: 9 additions & 2 deletions packages/flutter/lib/src/services/system_navigator.dart
Expand Up @@ -80,14 +80,21 @@ abstract final class SystemNavigator {
///
/// The `replace` flag defaults to false.
static Future<void> routeInformationUpdated({
required String location,
@Deprecated(
'Pass Uri.parse(location) to uri parameter instead. '
'This feature was deprecated after v3.8.0-3.0.pre.'
)
String? location,
Uri? uri,
Object? state,
bool replace = false,
}) {
assert((location != null) != (uri != null), 'One of uri or location must be provided, but not both.');
uri ??= Uri.parse(location!);
return SystemChannels.navigation.invokeMethod<void>(
'routeInformationUpdated',
<String, dynamic>{
'location': location,
'uri': uri.toString(),
'state': state,
'replace': replace,
},
Expand Down
15 changes: 12 additions & 3 deletions packages/flutter/lib/src/widgets/app.dart
Expand Up @@ -1362,7 +1362,7 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
if (widget.routeInformationProvider == null && widget.routeInformationParser != null) {
_defaultRouteInformationProvider ??= PlatformRouteInformationProvider(
initialRouteInformation: RouteInformation(
location: _initialRouteName,
uri: Uri.parse(_initialRouteName),
),
);
} else {
Expand Down Expand Up @@ -1484,7 +1484,7 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
}

@override
Future<bool> didPushRoute(String route) async {
Future<bool> didPushRouteInformation(RouteInformation routeInformation) async {
assert(mounted);
// The route name provider should handle the push route if we uses a
// router.
Expand All @@ -1496,7 +1496,16 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
if (navigator == null) {
return false;
}
navigator.pushNamed(route);
final Uri uri = routeInformation.uri;
navigator.pushNamed(
Uri.decodeComponent(
Uri(
path: uri.path.isEmpty ? '/' : uri.path,
queryParameters: uri.queryParametersAll.isEmpty ? null : uri.queryParametersAll,
fragment: uri.fragment.isEmpty ? null : uri.fragment,
).toString(),
),
);
return true;
}

Expand Down
35 changes: 24 additions & 11 deletions packages/flutter/lib/src/widgets/binding.dart
Expand Up @@ -72,6 +72,10 @@ abstract mixin class WidgetsBindingObserver {
///
/// This method exposes the `pushRoute` notification from
/// [SystemChannels.navigation].
@Deprecated(
'Use didPushRouteInformation instead. '
'This feature was deprecated after v3.8.0-14.0.pre.'
)
Future<bool> didPushRoute(String route) => Future<bool>.value(false);

/// Called when the host tells the application to push a new
Expand All @@ -85,9 +89,20 @@ abstract mixin class WidgetsBindingObserver {
/// [SystemChannels.navigation].
///
/// The default implementation is to call the [didPushRoute] directly with the
/// [RouteInformation.location].
/// string constructed from [RouteInformation.uri]'s path and query parameters.
// TODO(chunhtai): remove the default implementation once `didPushRoute` is
// removed.
Future<bool> didPushRouteInformation(RouteInformation routeInformation) {
return didPushRoute(routeInformation.location!);
final Uri uri = routeInformation.uri;
return didPushRoute(
Uri.decodeComponent(
Uri(
path: uri.path.isEmpty ? '/' : uri.path,
queryParameters: uri.queryParametersAll.isEmpty ? null : uri.queryParametersAll,
fragment: uri.fragment.isEmpty ? null : uri.fragment,
).toString(),
),
);
}

/// Called when the application's dimensions change. For example,
Expand Down Expand Up @@ -672,23 +687,21 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
@protected
@mustCallSuper
Future<void> handlePushRoute(String route) async {
final RouteInformation routeInformation = RouteInformation(uri: Uri.parse(route));
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
if (await observer.didPushRoute(route)) {
if (await observer.didPushRouteInformation(routeInformation)) {
return;
}
}
}

Future<void> _handlePushRouteInformation(Map<dynamic, dynamic> routeArguments) async {
final RouteInformation routeInformation = RouteInformation(
uri: Uri.parse(routeArguments['location'] as String),
state: routeArguments['state'] as Object?,
);
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
if (
await observer.didPushRouteInformation(
RouteInformation(
location: routeArguments['location'] as String,
state: routeArguments['state'] as Object?,
),
)
) {
if (await observer.didPushRouteInformation(routeInformation)) {
return;
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/flutter/lib/src/widgets/navigator.dart
Expand Up @@ -4050,7 +4050,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
);
final String? routeName = lastEntry?.route.settings.name;
if (routeName != null && routeName != _lastAnnouncedRouteName) {
SystemNavigator.routeInformationUpdated(location: routeName);
SystemNavigator.routeInformationUpdated(uri: Uri.parse(routeName));
_lastAnnouncedRouteName = routeName;
}
}
Expand Down
80 changes: 58 additions & 22 deletions packages/flutter/lib/src/widgets/router.dart
Expand Up @@ -41,18 +41,57 @@ import 'restoration_properties.dart';
class RouteInformation {
/// Creates a route information object.
///
/// The arguments may be null.
const RouteInformation({this.location, this.state});
/// Either location or uri must not be null.
const RouteInformation({
@Deprecated(
'Pass Uri.parse(location) to uri parameter instead. '
'This feature was deprecated after v3.8.0-3.0.pre.'
)
String? location,
Uri? uri,
this.state,
}) : _location = location,
_uri = uri,
assert((location != null) != (uri != null));

/// The location of the application.
///
/// The string is usually in the format of multiple string identifiers with
/// slashes in between. ex: `/`, `/path`, `/path/to/the/app`.
@Deprecated(
'Use uri instead. '
'This feature was deprecated after v3.8.0-3.0.pre.'
)
String get location {
if (_location != null) {
return _location!;
}
return Uri.decodeComponent(
Uri(
path: uri.path.isEmpty ? '/' : uri.path,
queryParameters: uri.queryParametersAll.isEmpty ? null : uri.queryParametersAll,
fragment: uri.fragment.isEmpty ? null : uri.fragment,
).toString(),
);
}
final String? _location;

/// The uri location of the application.
///
/// It is equivalent to the URL in a web application.
final String? location;
/// The host and scheme will not be empty if this object is created from a
/// deep link request. They represents the website that redirect the deep
/// link.
///
/// In web platform, the host and scheme are always empty.
Uri get uri {
if (_uri != null){
return _uri!;
}
return Uri.parse(_location!);
}
final Uri? _uri;

/// The state of the application in the [location].
/// The state of the application in the [uri].
///
/// The app can have different states even in the same location. For example,
/// the text inside a [TextField] or the scroll position in a [ScrollView].
Expand All @@ -61,11 +100,11 @@ class RouteInformation {
/// On the web, this information is stored in the browser history when the
/// [Router] reports this route information back to the web engine
/// through the [PlatformRouteInformationProvider]. The information
/// is then passed back, along with the [location], when the user
/// is then passed back, along with the [uri], when the user
/// clicks the back or forward buttons.
///
/// This information is also serialized and persisted alongside the
/// [location] for state restoration purposes. During state restoration,
/// [uri] for state restoration purposes. During state restoration,
/// the information is made available again to the [Router] so it can restore
/// its configuration to the previous state.
///
Expand Down Expand Up @@ -252,7 +291,7 @@ class RouterConfig<T> {
///
/// One can force the [Router] to report new route information as navigation
/// event to the [routeInformationProvider] (and thus the browser) even if the
/// [RouteInformation.location] has not changed by calling the [Router.navigate]
/// [RouteInformation.uri] has not changed by calling the [Router.navigate]
/// method with a callback that performs the state change. This causes [Router]
/// to call the [RouteInformationProvider.routerReportsNewRouteInformation] with
/// [RouteInformationReportingType.navigate], and thus causes
Expand Down Expand Up @@ -471,7 +510,7 @@ class Router<T> extends StatefulWidget {
///
/// The web application relies on the [Router] to report new route information
/// in order to create browser history entry. The [Router] will only report
/// them if it detects the [RouteInformation.location] changes. Use this
/// them if it detects the [RouteInformation.uri] changes. Use this
/// method if you want the [Router] to report the route information even if
/// the location does not change. This can be useful when you want to
/// support the browser backward and forward button without changing the URL.
Expand Down Expand Up @@ -502,7 +541,7 @@ class Router<T> extends StatefulWidget {
///
/// The web application relies on the [Router] to report new route information
/// in order to create browser history entry. The [Router] will report them
/// automatically if it detects the [RouteInformation.location] changes.
/// automatically if it detects the [RouteInformation.uri] changes.
///
/// Creating a new route history entry makes users feel they have visited a
/// new page, and the browser back button brings them back to previous history
Expand Down Expand Up @@ -1432,10 +1471,10 @@ class PlatformRouteInformationProvider extends RouteInformationProvider with Wid
final bool replace =
type == RouteInformationReportingType.neglect ||
(type == RouteInformationReportingType.none &&
_valueInEngine.location == routeInformation.location);
_valueInEngine.uri == routeInformation.uri);
SystemNavigator.selectMultiEntryHistory();
SystemNavigator.routeInformationUpdated(
location: routeInformation.location!,
uri: routeInformation.uri,
state: routeInformation.state,
replace: replace,
);
Expand All @@ -1447,7 +1486,7 @@ class PlatformRouteInformationProvider extends RouteInformationProvider with Wid
RouteInformation get value => _value;
RouteInformation _value;

RouteInformation _valueInEngine = RouteInformation(location: WidgetsBinding.instance.platformDispatcher.defaultRouteName);
RouteInformation _valueInEngine = RouteInformation(uri: Uri.parse(WidgetsBinding.instance.platformDispatcher.defaultRouteName));

void _platformReportsNewRouteInformation(RouteInformation routeInformation) {
if (_value == routeInformation) {
Expand Down Expand Up @@ -1492,13 +1531,6 @@ class PlatformRouteInformationProvider extends RouteInformationProvider with Wid
_platformReportsNewRouteInformation(routeInformation);
return true;
}

@override
Future<bool> didPushRoute(String route) async {
assert(hasListeners);
_platformReportsNewRouteInformation(RouteInformation(location: route));
return true;
}
}

/// A mixin that wires [RouterDelegate.popRoute] to the [Navigator] it builds.
Expand Down Expand Up @@ -1542,11 +1574,15 @@ class _RestorableRouteInformation extends RestorableValue<RouteInformation?> {
}
assert(data is List<Object?> && data.length == 2);
final List<Object?> castedData = data as List<Object?>;
return RouteInformation(location: castedData.first as String?, state: castedData.last);
final String? uri = castedData.first as String?;
if (uri == null) {
return null;
}
return RouteInformation(uri: Uri.parse(uri), state: castedData.last);
}

@override
Object? toPrimitives() {
return value == null ? null : <Object?>[value!.location, value!.state];
return value == null ? null : <Object?>[value!.uri.toString(), value!.state];
}
}

0 comments on commit 3b4ac4d

Please sign in to comment.