From 4afcb88e8b8c1bf7ddf4b317c9f9a260739dabb1 Mon Sep 17 00:00:00 2001 From: Lorenzo Hidalgo Date: Sat, 27 Aug 2022 13:58:49 +0200 Subject: [PATCH 1/5] added autmatic uri constructor for queryParameters --- package/lib/src/beamer.dart | 11 +++++--- .../lib/src/path_url_query_parameters.dart | 20 ++++++++++++++ .../test/path_url_query_parameters_test.dart | 27 +++++++++++++++++++ 3 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 package/lib/src/path_url_query_parameters.dart create mode 100644 package/test/path_url_query_parameters_test.dart diff --git a/package/lib/src/beamer.dart b/package/lib/src/beamer.dart index de55902..4dff022 100644 --- a/package/lib/src/beamer.dart +++ b/package/lib/src/beamer.dart @@ -4,6 +4,8 @@ import 'package:flutter/widgets.dart'; import 'package:beamer/src/path_url_strategy_nonweb.dart' if (dart.library.html) 'path_url_strategy_web.dart' as url_strategy; +part 'path_url_query_parameters.dart'; + /// Represents a navigation area and is a wrapper for [Router]. /// /// This is most commonly used for "nested navigation", e.g. in a tabbed view. @@ -153,6 +155,7 @@ extension BeamerExtensions on BuildContext { String uri, { Object? routeState, Object? data, + Map? queryParameters, String? popToNamed, TransitionDelegate? transitionDelegate, bool beamBackOnPop = false, @@ -161,7 +164,7 @@ extension BeamerExtensions on BuildContext { bool replaceRouteInformation = false, }) { Beamer.of(this).beamToNamed( - uri, + constructUri(uri, queryParameters), routeState: routeState, data: data, popToNamed: popToNamed, @@ -178,6 +181,7 @@ extension BeamerExtensions on BuildContext { String uri, { Object? routeState, Object? data, + Map? queryParameters, String? popToNamed, TransitionDelegate? transitionDelegate, bool beamBackOnPop = false, @@ -185,7 +189,7 @@ extension BeamerExtensions on BuildContext { bool stacked = true, }) { Beamer.of(this).beamToReplacementNamed( - uri, + constructUri(uri, queryParameters), routeState: routeState, data: data, popToNamed: popToNamed, @@ -201,6 +205,7 @@ extension BeamerExtensions on BuildContext { String uri, { Object? routeState, Object? data, + Map? queryParameters, String? popToNamed, bool beamBackOnPop = false, bool popBeamLocationOnPop = false, @@ -208,7 +213,7 @@ extension BeamerExtensions on BuildContext { bool replaceRouteInformation = false, }) { Beamer.of(this).popToNamed( - uri, + constructUri(uri, queryParameters), routeState: routeState, data: data, popToNamed: popToNamed, diff --git a/package/lib/src/path_url_query_parameters.dart b/package/lib/src/path_url_query_parameters.dart new file mode 100644 index 0000000..ff63ba6 --- /dev/null +++ b/package/lib/src/path_url_query_parameters.dart @@ -0,0 +1,20 @@ +part of 'beamer.dart'; + +/// Returns the provided `String uri` with the constructed `Map? queryParameters` +/// +/// If `Map? queryParameters` is provided, `String uri` should not containt query parameters. +/// +/// When `queryParameters` is used the query is built from the provided map. +/// Each key and value in the map is percent-encoded and joined using equal and ampersand characters. +/// A value in the map must be either a `String`, or an `Iterable`, where the latter corresponds to multiple values for the same key. +@visibleForTesting +String constructUri(String uri, Map? queryParameters) { + final _inputQueryParameters = Uri.parse(uri).queryParameters; + assert(_inputQueryParameters.isEmpty || (queryParameters?.isEmpty ?? true), + 'Avoid passing an uri that already contains query Parameters and a non-empty `queryParameters`'); + + if (queryParameters?.isEmpty ?? true) return uri; + if (_inputQueryParameters.isNotEmpty) return uri; + + return uri + '?' + Uri(queryParameters: queryParameters).query; +} diff --git a/package/test/path_url_query_parameters_test.dart b/package/test/path_url_query_parameters_test.dart new file mode 100644 index 0000000..edfe9df --- /dev/null +++ b/package/test/path_url_query_parameters_test.dart @@ -0,0 +1,27 @@ +import 'package:beamer/beamer.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test( + 'Only URI was passed', + () { + expect(constructUri('/test', null), '/test'); + }, + ); + + test( + 'URI without queryParameters and queryParameters Map were passed', + () { + expect(constructUri('/test', {'test': 'true'}), '/test?test=true'); + }, + ); + + test( + 'URI wit queryParameters and queryParameters Map were passed', + () { + expect(() { + constructUri('/test?test=true', {'test': 'true'}); + }, throwsAssertionError); + }, + ); +} From ba529247ea5d59a3d7d97d9f45e70c93039cf435 Mon Sep 17 00:00:00 2001 From: Lorenzo Hidalgo Date: Wed, 31 Aug 2022 10:04:11 +0200 Subject: [PATCH 2/5] updated beamer_test --- package/test/beamer_test.dart | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/package/test/beamer_test.dart b/package/test/beamer_test.dart index 4d2d06d..3069264 100644 --- a/package/test/beamer_test.dart +++ b/package/test/beamer_test.dart @@ -36,6 +36,7 @@ void main() { key: beamerKey, routerDelegate: BeamerProvider.of(context)!.routerDelegate, ), + '/test3': (context, state, data) => Container(), }, ), ), @@ -170,4 +171,20 @@ void main() { await tester.pumpWidget(app); expect(testContext!.beamingHistory.length, greaterThan(0)); }); + + testWidgets('Beaming updates location state with queryParameters', + (tester) async { + const queryParams = {'testParam': 'test'}; + await tester.pumpWidget(app); + testContext!.beamToReplacementNamed('/test3', queryParameters: queryParams); + await tester.pump(); + expect(testContext!.currentBeamLocation.state.routeInformation.location, + '/test3?testParam=test'); + expect( + (testContext!.currentBeamLocation.state as BeamState) + .uriBlueprint + .toString(), + '/test3?testParam=test'); + expect(testContext!.currentBeamPages.length, 2); + }); } From a76118edb2f2f28f62ffb32a40731cd5659f4628 Mon Sep 17 00:00:00 2001 From: Lorenzo Hidalgo Date: Wed, 31 Aug 2022 10:09:44 +0200 Subject: [PATCH 3/5] fixed: 'hashValues' is deprecated and shouldn't be used. Use Object.hash() instead. --- package/lib/src/beam_state.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/lib/src/beam_state.dart b/package/lib/src/beam_state.dart index af33ea7..a1371dd 100644 --- a/package/lib/src/beam_state.dart +++ b/package/lib/src/beam_state.dart @@ -196,7 +196,7 @@ class BeamState with RouteInformationSerializable { ); @override - int get hashCode => hashValues(uri, json.encode(routeState)); + int get hashCode => Object.hash(uri, json.encode(routeState)); @override bool operator ==(Object other) { From ec9006c958479665251bc9fe9be396910e770a13 Mon Sep 17 00:00:00 2001 From: Lorenzo Hidalgo Date: Mon, 17 Oct 2022 11:36:37 +0200 Subject: [PATCH 4/5] moved `constructUri` to `utils.dart` --- package/lib/src/beamer.dart | 3 +-- .../lib/src/path_url_query_parameters.dart | 20 ------------------- package/lib/src/utils.dart | 18 +++++++++++++++++ .../test/path_url_query_parameters_test.dart | 2 +- 4 files changed, 20 insertions(+), 23 deletions(-) delete mode 100644 package/lib/src/path_url_query_parameters.dart diff --git a/package/lib/src/beamer.dart b/package/lib/src/beamer.dart index 4dff022..67ecd38 100644 --- a/package/lib/src/beamer.dart +++ b/package/lib/src/beamer.dart @@ -1,11 +1,10 @@ import 'package:beamer/beamer.dart'; +import 'package:beamer/src/utils.dart'; import 'package:flutter/widgets.dart'; import 'package:beamer/src/path_url_strategy_nonweb.dart' if (dart.library.html) 'path_url_strategy_web.dart' as url_strategy; -part 'path_url_query_parameters.dart'; - /// Represents a navigation area and is a wrapper for [Router]. /// /// This is most commonly used for "nested navigation", e.g. in a tabbed view. diff --git a/package/lib/src/path_url_query_parameters.dart b/package/lib/src/path_url_query_parameters.dart deleted file mode 100644 index ff63ba6..0000000 --- a/package/lib/src/path_url_query_parameters.dart +++ /dev/null @@ -1,20 +0,0 @@ -part of 'beamer.dart'; - -/// Returns the provided `String uri` with the constructed `Map? queryParameters` -/// -/// If `Map? queryParameters` is provided, `String uri` should not containt query parameters. -/// -/// When `queryParameters` is used the query is built from the provided map. -/// Each key and value in the map is percent-encoded and joined using equal and ampersand characters. -/// A value in the map must be either a `String`, or an `Iterable`, where the latter corresponds to multiple values for the same key. -@visibleForTesting -String constructUri(String uri, Map? queryParameters) { - final _inputQueryParameters = Uri.parse(uri).queryParameters; - assert(_inputQueryParameters.isEmpty || (queryParameters?.isEmpty ?? true), - 'Avoid passing an uri that already contains query Parameters and a non-empty `queryParameters`'); - - if (queryParameters?.isEmpty ?? true) return uri; - if (_inputQueryParameters.isNotEmpty) return uri; - - return uri + '?' + Uri(queryParameters: queryParameters).query; -} diff --git a/package/lib/src/utils.dart b/package/lib/src/utils.dart index b40c7cd..6a50bc9 100644 --- a/package/lib/src/utils.dart +++ b/package/lib/src/utils.dart @@ -309,3 +309,21 @@ extension BeamerRouteInformationExtension on RouteInformation { return location == other.location && state == other.state; } } + +/// Returns the provided `String uri` with the constructed `Map? queryParameters` +/// +/// If `Map? queryParameters` is provided, `String uri` should not containt query parameters. +/// +/// When `queryParameters` is used the query is built from the provided map. +/// Each key and value in the map is percent-encoded and joined using equal and ampersand characters. +/// A value in the map must be either a `String`, or an `Iterable`, where the latter corresponds to multiple values for the same key. +String constructUri(String uri, Map? queryParameters) { + final _inputQueryParameters = Uri.parse(uri).queryParameters; + assert(_inputQueryParameters.isEmpty || (queryParameters?.isEmpty ?? true), + 'Avoid passing an uri that already contains query Parameters and a non-empty `queryParameters`'); + + if (queryParameters?.isEmpty ?? true) return uri; + if (_inputQueryParameters.isNotEmpty) return uri; + + return uri + '?' + Uri(queryParameters: queryParameters).query; +} diff --git a/package/test/path_url_query_parameters_test.dart b/package/test/path_url_query_parameters_test.dart index edfe9df..1719ba9 100644 --- a/package/test/path_url_query_parameters_test.dart +++ b/package/test/path_url_query_parameters_test.dart @@ -1,4 +1,4 @@ -import 'package:beamer/beamer.dart'; +import 'package:beamer/src/utils.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { From 738b002eebe0fa611da2e8dd58af4de09d9e2f5b Mon Sep 17 00:00:00 2001 From: Lorenzo Hidalgo Date: Mon, 17 Oct 2022 11:41:17 +0200 Subject: [PATCH 5/5] updated `BeamerDelegate` --- package/lib/src/beamer_delegate.dart | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/package/lib/src/beamer_delegate.dart b/package/lib/src/beamer_delegate.dart index 5c13d27..8d4804b 100644 --- a/package/lib/src/beamer_delegate.dart +++ b/package/lib/src/beamer_delegate.dart @@ -546,9 +546,13 @@ class BeamerDelegate extends RouterDelegate bool popBeamLocationOnPop = false, bool stacked = true, bool replaceRouteInformation = false, + Map? queryParameters, }) { update( - configuration: RouteInformation(location: uri, state: routeState), + configuration: RouteInformation( + location: constructUri(uri, queryParameters), + state: routeState, + ), beamParameters: _currentBeamParameters.copyWith( popConfiguration: popToNamed != null ? RouteInformation(location: popToNamed) : null, @@ -573,10 +577,11 @@ class BeamerDelegate extends RouterDelegate bool beamBackOnPop = false, bool popBeamLocationOnPop = false, bool stacked = true, + Map? queryParameters, }) { removeLastHistoryElement(); beamToNamed( - uri, + constructUri(uri, queryParameters), routeState: routeState, data: data, popToNamed: popToNamed, @@ -604,10 +609,12 @@ class BeamerDelegate extends RouterDelegate bool popBeamLocationOnPop = false, bool stacked = true, bool replaceRouteInformation = false, + Map? queryParameters, }) { + final fullUri = constructUri(uri, queryParameters); while (beamingHistory.isNotEmpty) { final index = beamingHistory.last.history.lastIndexWhere( - (element) => element.routeInformation.location == uri, + (element) => element.routeInformation.location == fullUri, ); if (index == -1) { _disposeBeamLocation(beamingHistory.last); @@ -620,7 +627,7 @@ class BeamerDelegate extends RouterDelegate } } beamToNamed( - uri, + fullUri, routeState: routeState, data: data, popToNamed: popToNamed,