diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md
index f732fa58f8c6..e1dd18f18f6e 100644
--- a/packages/url_launcher/url_launcher/CHANGELOG.md
+++ b/packages/url_launcher/url_launcher/CHANGELOG.md
@@ -1,6 +1,12 @@
+## 6.1.0
+
+* Add a utility method for query encoding when constructing
+ URIs, and update the README accordingly, to work around limitations
+ in `Uri` behavior.
+
## 6.0.3
-* Updat README notes about URL schemes on iOS
+* Update README notes about URL schemes on iOS
## 6.0.2
diff --git a/packages/url_launcher/url_launcher/README.md b/packages/url_launcher/url_launcher/README.md
index 31fed9a833f1..d8638f5ef2c7 100644
--- a/packages/url_launcher/url_launcher/README.md
+++ b/packages/url_launcher/url_launcher/README.md
@@ -10,10 +10,10 @@ To use this plugin, add `url_launcher` as a [dependency in your pubspec.yaml fil
## Installation
-### iOS
+### iOS
Add any URL schemes passed to `canLaunch` as `LSApplicationQueriesSchemes` entries in your Info.plist file.
-Example:
+Example:
```
LSApplicationQueriesSchemes
@@ -73,25 +73,37 @@ apps installed, so can't open `tel:` or `mailto:` links.
### Encoding URLs
-URLs must be properly encoded, especially when including spaces or other special characters. This can be done using the [`Uri` class](https://api.dart.dev/stable/2.7.1/dart-core/Uri-class.html):
+URLs must be properly encoded, especially when including spaces or other special
+characters. This can be done using the
+[`Uri` class](https://api.dart.dev/stable/2.7.1/dart-core/Uri-class.html).
+For example:
```dart
-import 'dart:core';
-import 'package:url_launcher/url_launcher.dart';
-
-final Uri _emailLaunchUri = Uri(
+final Uri emailLaunchUri = Uri(
scheme: 'mailto',
path: 'smith@example.com',
- queryParameters: {
+ query: encodeQueryParameters({
'subject': 'Example Subject & Symbols are allowed!'
- }
+ }),
);
-// ...
+launch(emailLaunchUri.toString());
+```
+
+**Warning**: For any scheme other than `http` or `https`, you should use this
+package's utility method for query parameters:
-// mailto:smith@example.com?subject=Example+Subject+%26+Symbols+are+allowed%21
-launch(_emailLaunchUri.toString());
+```dart
+Uri(
+ // ...
+ query: encodeQueryParameters(yourParameters),
+);
```
+rather than `Uri`'s `queryParameters` constructor argument, due to
+[a bug](https://github.com/dart-lang/sdk/issues/43838) in the way `Uri`
+encodes query parameters. Using `queryParameters` will result in spaces being
+converted to `+` in many cases.
+
## Handling missing URL receivers
A particular mobile device may not be able to receive all supported URL schemes.
@@ -113,4 +125,4 @@ By default, Android opens up a browser when handling URLs. You can pass
If you do this for a URL of a page containing JavaScript, make sure to pass in
`enableJavaScript: true`, or else the launch method will not work properly. On
iOS, the default behavior is to open all web URLs within the app. Everything
-else is redirected to the app handler.
\ No newline at end of file
+else is redirected to the app handler.
diff --git a/packages/url_launcher/url_launcher/example/linux/flutter/generated_plugin_registrant.cc b/packages/url_launcher/url_launcher/example/linux/flutter/generated_plugin_registrant.cc
index 36185a63f2fd..026851fa2f96 100644
--- a/packages/url_launcher/url_launcher/example/linux/flutter/generated_plugin_registrant.cc
+++ b/packages/url_launcher/url_launcher/example/linux/flutter/generated_plugin_registrant.cc
@@ -8,7 +8,6 @@
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
- fl_plugin_registry_get_registrar_for_plugin(registry,
- "UrlLauncherPlugin");
+ fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
}
diff --git a/packages/url_launcher/url_launcher/lib/url_launcher.dart b/packages/url_launcher/url_launcher/lib/url_launcher.dart
index b59c91d02a1a..d32aa81f36f5 100644
--- a/packages/url_launcher/url_launcher/lib/url_launcher.dart
+++ b/packages/url_launcher/url_launcher/lib/url_launcher.dart
@@ -9,6 +9,24 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
+/// Encodes [params] as a query parameter string suitable to be passed as the
+/// 'query' parameter to a [Uri] constructor.
+///
+/// This exists to work around the fact that the 'queryParameters' argument of
+/// the [Uri] constructor does encoding as HTML form parameters rather than
+/// generic URI query parameters, and thus does not work correctly for schemes
+/// other than http(s). See https://github.com/dart-lang/sdk/issues/43838 for
+/// details.
+String? encodeQueryParameters(Map params) {
+ if (params.isEmpty) {
+ return null;
+ }
+ return params.entries
+ .map((MapEntry e) =>
+ '${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}')
+ .join('&');
+}
+
/// Parses the specified URL string and delegates handling of it to the
/// underlying platform.
///
diff --git a/packages/url_launcher/url_launcher/pubspec.yaml b/packages/url_launcher/url_launcher/pubspec.yaml
index c8c5163d0e97..ed146cf08031 100644
--- a/packages/url_launcher/url_launcher/pubspec.yaml
+++ b/packages/url_launcher/url_launcher/pubspec.yaml
@@ -2,7 +2,7 @@ name: url_launcher
description: Flutter plugin for launching a URL. Supports
web, phone, SMS, and email schemes.
homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher
-version: 6.0.3
+version: 6.1.0
flutter:
plugin:
diff --git a/packages/url_launcher/url_launcher/test/url_launcher_test.dart b/packages/url_launcher/url_launcher/test/url_launcher_test.dart
index 9b2d167483cd..b0289e4a21cd 100644
--- a/packages/url_launcher/url_launcher/test/url_launcher_test.dart
+++ b/packages/url_launcher/url_launcher/test/url_launcher_test.dart
@@ -282,4 +282,58 @@ void main() {
expect(binding.renderView.automaticSystemUiAdjustment, true);
});
});
+
+ group('encodeQueryParameters', () {
+ test('handles empty dictionary', () async {
+ final String? result = encodeQueryParameters({});
+
+ expect(result, isNull);
+ });
+
+ test('handles parameters without special characters', () async {
+ final Map parameters = {
+ 'key1': 'value1',
+ 'key2': 'value2',
+ };
+ final String? result = encodeQueryParameters(parameters);
+
+ expect(Uri.splitQueryString(result!), parameters);
+ });
+
+ test('handles & correctly', () async {
+ final Map parameters = {
+ 'key1': 'this & that',
+ 'key&': 'foo & bar',
+ };
+ final String? result = encodeQueryParameters(parameters);
+
+ expect(Uri.splitQueryString(result!), parameters);
+ // There should be exactly one unencoded & in the string, joining the
+ // two parameters.
+ expect(result.split('&').length, 2);
+ });
+
+ test('handles spaces correctly', () async {
+ final Map parameters = {
+ 'a key': 'a value',
+ };
+ final String? result = encodeQueryParameters(parameters);
+
+ expect(Uri.splitQueryString(result!), parameters);
+ // Spaces should be encoded as %20, not +.
+ expect(result.contains('+'), isFalse);
+ expect(result.contains('%20'), isTrue);
+ });
+
+ test('handles + correctly', () async {
+ final Map parameters = {
+ 'key+': 'value+',
+ };
+ final String? result = encodeQueryParameters(parameters);
+
+ expect(Uri.splitQueryString(result!), parameters);
+ // + should be encoded.
+ expect(result.contains('+'), isFalse);
+ });
+ });
}