Skip to content

Commit

Permalink
PlatformRouteInformationProvider does not push new entry if query par…
Browse files Browse the repository at this point in the history
…… (#130457)

�ameter is semanticsally the same

The URI compare does not taking into account that query parameter may or may not be encoded, or the parameters' order can be different. However, they are all semantically the same. 

This pr makes PlatformRouteInformationProvider to take those into account when deciding whether it should push/replace the browser history entry.
  • Loading branch information
chunhtai authored Jul 13, 2023
1 parent 5870159 commit 315ebaf
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 1 deletion.
9 changes: 8 additions & 1 deletion packages/flutter/lib/src/widgets/router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import 'dart:async';
import 'dart:collection';

import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
Expand Down Expand Up @@ -1466,12 +1467,18 @@ class PlatformRouteInformationProvider extends RouteInformationProvider with Wid
required RouteInformation initialRouteInformation,
}) : _value = initialRouteInformation;

static bool _equals(Uri a, Uri b) {
return a.path == b.path
&& a.fragment == b.fragment
&& const DeepCollectionEquality.unordered().equals(a.queryParametersAll, b.queryParametersAll);
}

@override
void routerReportsNewRouteInformation(RouteInformation routeInformation, {RouteInformationReportingType type = RouteInformationReportingType.none}) {
final bool replace =
type == RouteInformationReportingType.neglect ||
(type == RouteInformationReportingType.none &&
_valueInEngine.uri == routeInformation.uri);
_equals(_valueInEngine.uri, routeInformation.uri));
SystemNavigator.selectMultiEntryHistory();
SystemNavigator.routeInformationUpdated(
uri: routeInformation.uri,
Expand Down
70 changes: 70 additions & 0 deletions packages/flutter/test/widgets/router_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,76 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester
]);
});

testWidgets('PlatformRouteInformationProvider does not push new entry if query parameters are semantically the same', (WidgetTester tester) async {
final List<MethodCall> log = <MethodCall>[];
TestDefaultBinaryMessengerBinding
.instance.defaultBinaryMessenger
.setMockMethodCallHandler(
SystemChannels.navigation,
(MethodCall methodCall) async {
log.add(methodCall);
return null;
}
);
final RouteInformation initial = RouteInformation(
uri: Uri.parse('initial?a=ws/abcd'),
);
final RouteInformationProvider provider = PlatformRouteInformationProvider(
initialRouteInformation: initial
);
// Make sure engine is updated with initial route
provider.routerReportsNewRouteInformation(initial);
log.clear();

provider.routerReportsNewRouteInformation(
RouteInformation(
uri: Uri(
path: 'initial',
queryParameters: <String, String>{'a': 'ws/abcd'}, // This will be escaped.
),
),
);
expect(provider.value.uri.toString(), 'initial?a=ws%2Fabcd');
// should use `replace: true`
expect(log, <Object>[
isMethodCall('selectMultiEntryHistory', arguments: null),
isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{ 'uri': 'initial?a=ws%2Fabcd', 'state': null, 'replace': true }),
]);
log.clear();

provider.routerReportsNewRouteInformation(
RouteInformation(uri: Uri.parse('initial?a=1&b=2')),
);
log.clear();

// Change query parameters order
provider.routerReportsNewRouteInformation(
RouteInformation(uri: Uri.parse('initial?b=2&a=1')),
);
// should use `replace: true`
expect(log, <Object>[
isMethodCall('selectMultiEntryHistory', arguments: null),
isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{ 'uri': 'initial?b=2&a=1', 'state': null, 'replace': true }),
]);
log.clear();

provider.routerReportsNewRouteInformation(
RouteInformation(uri: Uri.parse('initial?a=1&a=2')),
);
log.clear();

// Change query parameters order for same key
provider.routerReportsNewRouteInformation(
RouteInformation(uri: Uri.parse('initial?a=2&a=1')),
);
// should use `replace: true`
expect(log, <Object>[
isMethodCall('selectMultiEntryHistory', arguments: null),
isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{ 'uri': 'initial?a=2&a=1', 'state': null, 'replace': true }),
]);
log.clear();
});

testWidgets('RootBackButtonDispatcher works', (WidgetTester tester) async {
final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher();
final RouteInformationProvider provider = PlatformRouteInformationProvider(
Expand Down

0 comments on commit 315ebaf

Please sign in to comment.