[url_launcher] Improve README and example (#4529)

Fixes the README and example to properly demonstrate using `canLaunch`
to adjust UI, rather than as a direct wrapper around `launch` (which
serves no useful purpose given that `launch` can already return
failure).

Also makes some minor organizational improvements to the README (e.g.,
fixing the fact that the discussions of iOS and Android configuration
were separated by the inline example for some reason).
diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md
index e69083d..7aab663 100644
--- a/packages/url_launcher/url_launcher/CHANGELOG.md
+++ b/packages/url_launcher/url_launcher/CHANGELOG.md
@@ -1,3 +1,10 @@
+## 6.0.15
+
+* Updates README:
+  * Improves organization.
+  * Clarifies how `canLaunch` should be used.
+* Updates example application to demonstrate intended use of `canLaunch`.
+
 ## 6.0.14
 
 * Updates readme to indicate that sending SMS messages on Android 11 requires to add a query to AndroidManifest.xml.
diff --git a/packages/url_launcher/url_launcher/README.md b/packages/url_launcher/url_launcher/README.md
index aa16132..393bbd9 100644
--- a/packages/url_launcher/url_launcher/README.md
+++ b/packages/url_launcher/url_launcher/README.md
@@ -6,31 +6,16 @@
 iOS, Android, web, Windows, macOS, and Linux.
 
 ## Usage
+
 To use this plugin, add `url_launcher` as a [dependency in your pubspec.yaml file](https://flutter.dev/platform-plugins/).
 
-## Installation
-
-### iOS
-Add any URL schemes passed to `canLaunch` as `LSApplicationQueriesSchemes` entries in your Info.plist file.
-
-Example:
-```
-<key>LSApplicationQueriesSchemes</key>
-<array>
-  <string>https</string>
-  <string>http</string>
-</array>
-```
-
-See [`-[UIApplication canOpenURL:]`](https://developer.apple.com/documentation/uikit/uiapplication/1622952-canopenurl) for more details.
-
 ### Example
 
 ``` dart
 import 'package:flutter/material.dart';
 import 'package:url_launcher/url_launcher.dart';
 
-const _url = 'https://flutter.dev';
+const String _url = 'https://flutter.dev';
 
 void main() => runApp(
       const MaterialApp(
@@ -45,10 +30,29 @@
       ),
     );
 
-void _launchURL() async =>
-    await canLaunch(_url) ? await launch(_url) : throw 'Could not launch $_url';
+void _launchURL() async {
+  if (!await launch(_url)) throw 'Could not launch $_url';
+}
 ```
 
+See the example app for more complex examples.
+
+## Configuration
+
+### iOS
+Add any URL schemes passed to `canLaunch` as `LSApplicationQueriesSchemes` entries in your Info.plist file.
+
+Example:
+```
+<key>LSApplicationQueriesSchemes</key>
+<array>
+  <string>https</string>
+  <string>http</string>
+</array>
+```
+
+See [`-[UIApplication canOpenURL:]`](https://developer.apple.com/documentation/uikit/uiapplication/1622952-canopenurl) for more details.
+
 ### Android
 
 Starting from API 30 Android requires package visibility configuration in your
@@ -138,20 +142,20 @@
 encodes query parameters. Using `queryParameters` will result in spaces being
 converted to `+` in many cases.
 
-## Handling missing URL receivers
+### Handling missing URL receivers
 
 A particular mobile device may not be able to receive all supported URL schemes.
 For example, a tablet may not have a cellular radio and thus no support for
 launching a URL using the `sms` scheme, or a device may not have an email app
-and thus no support for launching a URL using the `email` scheme.
+and thus no support for launching a URL using the `mailto` scheme.
 
 We recommend checking which URL schemes are supported using the
 [`canLaunch`](https://pub.dev/documentation/url_launcher/latest/url_launcher/canLaunch.html)
-method prior to calling `launch`. If the `canLaunch` method returns false, as a
+in most cases. If the `canLaunch` method returns false, as a
 best practice we suggest adjusting the application UI so that the unsupported
-URL is never triggered; for example, if the `email` scheme is not supported, a
-UI button that would have sent email can be changed to redirect the user to a
-web page using a URL following the `http` scheme.
+URL is never triggered; for example, if the `mailto` scheme is not supported, a
+UI button that would have sent feedback email could be changed to instead open
+a web-based feedback form using an `https` URL.
 
 ## Browser vs In-app Handling
 By default, Android opens up a browser when handling URLs. You can pass
diff --git a/packages/url_launcher/url_launcher/example/lib/main.dart b/packages/url_launcher/url_launcher/example/lib/main.dart
index d593e6d..7e4d18a 100644
--- a/packages/url_launcher/url_launcher/example/lib/main.dart
+++ b/packages/url_launcher/url_launcher/example/lib/main.dart
@@ -36,74 +36,76 @@
 }
 
 class _MyHomePageState extends State<MyHomePage> {
+  bool _hasCallSupport = false;
   Future<void>? _launched;
   String _phone = '';
 
+  @override
+  void initState() {
+    super.initState();
+    // Check for phone call support.
+    canLaunch('tel:123').then((bool result) {
+      setState(() {
+        _hasCallSupport = result;
+      });
+    });
+  }
+
   Future<void> _launchInBrowser(String url) async {
-    if (await canLaunch(url)) {
-      await launch(
-        url,
-        forceSafariVC: false,
-        forceWebView: false,
-        headers: <String, String>{'my_header_key': 'my_header_value'},
-      );
-    } else {
+    if (!await launch(
+      url,
+      forceSafariVC: false,
+      forceWebView: false,
+      headers: <String, String>{'my_header_key': 'my_header_value'},
+    )) {
       throw 'Could not launch $url';
     }
   }
 
   Future<void> _launchInWebViewOrVC(String url) async {
-    if (await canLaunch(url)) {
-      await launch(
-        url,
-        forceSafariVC: true,
-        forceWebView: true,
-        headers: <String, String>{'my_header_key': 'my_header_value'},
-      );
-    } else {
+    if (!await launch(
+      url,
+      forceSafariVC: true,
+      forceWebView: true,
+      headers: <String, String>{'my_header_key': 'my_header_value'},
+    )) {
       throw 'Could not launch $url';
     }
   }
 
   Future<void> _launchInWebViewWithJavaScript(String url) async {
-    if (await canLaunch(url)) {
-      await launch(
-        url,
-        forceSafariVC: true,
-        forceWebView: true,
-        enableJavaScript: true,
-      );
-    } else {
+    if (!await launch(
+      url,
+      forceSafariVC: true,
+      forceWebView: true,
+      enableJavaScript: true,
+    )) {
       throw 'Could not launch $url';
     }
   }
 
   Future<void> _launchInWebViewWithDomStorage(String url) async {
-    if (await canLaunch(url)) {
-      await launch(
-        url,
-        forceSafariVC: true,
-        forceWebView: true,
-        enableDomStorage: true,
-      );
-    } else {
+    if (!await launch(
+      url,
+      forceSafariVC: true,
+      forceWebView: true,
+      enableDomStorage: true,
+    )) {
       throw 'Could not launch $url';
     }
   }
 
   Future<void> _launchUniversalLinkIos(String url) async {
-    if (await canLaunch(url)) {
-      final bool nativeAppLaunchSucceeded = await launch(
+    final bool nativeAppLaunchSucceeded = await launch(
+      url,
+      forceSafariVC: false,
+      universalLinksOnly: true,
+    );
+    if (!nativeAppLaunchSucceeded) {
+      await launch(
         url,
-        forceSafariVC: false,
-        universalLinksOnly: true,
+        forceSafariVC: true,
       );
-      if (!nativeAppLaunchSucceeded) {
-        await launch(
-          url,
-          forceSafariVC: true,
-        );
-      }
     }
   }
 
@@ -115,16 +117,22 @@
     }
   }
 
-  Future<void> _makePhoneCall(String url) async {
-    if (await canLaunch(url)) {
-      await launch(url);
-    } else {
-      throw 'Could not launch $url';
-    }
+  Future<void> _makePhoneCall(String phoneNumber) async {
+    // Use `Uri` to ensure that `phoneNumber` is properly URL-encoded.
+    // Just using 'tel:$phoneNumber' would create invalid URLs in some cases,
+    // such as spaces in the input, which would cause `launch` to fail on some
+    // platforms.
+    final Uri launchUri = Uri(
+      scheme: 'tel',
+      path: phoneNumber,
+    );
+    await launch(launchUri.toString());
   }
 
   @override
   Widget build(BuildContext context) {
+    // onPressed calls using this URL are not gated on a 'canLaunch' check
+    // because the assumption is that every device can launch a web URL.
     const String toLaunch = 'https://www.cylog.org/headers/';
     return Scaffold(
       appBar: AppBar(
@@ -143,10 +151,14 @@
                         hintText: 'Input the phone number to launch')),
               ),
               ElevatedButton(
-                onPressed: () => setState(() {
-                  _launched = _makePhoneCall('tel:$_phone');
-                }),
-                child: const Text('Make phone call'),
+                onPressed: _hasCallSupport
+                    ? () => setState(() {
+                          _launched = _makePhoneCall(_phone);
+                        })
+                    : null,
+                child: _hasCallSupport
+                    ? const Text('Make phone call')
+                    : const Text('Calling not supported'),
               ),
               const Padding(
                 padding: EdgeInsets.all(16.0),
diff --git a/packages/url_launcher/url_launcher/lib/url_launcher.dart b/packages/url_launcher/url_launcher/lib/url_launcher.dart
index 300f96f..f28c460 100644
--- a/packages/url_launcher/url_launcher/lib/url_launcher.dart
+++ b/packages/url_launcher/url_launcher/lib/url_launcher.dart
@@ -122,12 +122,11 @@
 /// Checks whether the specified URL can be handled by some app installed on the
 /// device.
 ///
-/// On Android (from API 30), [canLaunch] will return `false` when the required
-/// visibility configuration is not provided in the AndroidManifest.xml file.
-/// For more information see the
-/// [Package visibility filtering on Android](https://developer.android.com/training/basics/intents/package-visibility)
-/// article in the Android documentation or the url_launcher example app's
-/// [AndroidManifest.xml's queries element](https://github.com/flutter/plugins/blob/master/packages/url_launcher/url_launcher/example/android/app/src/main/AndroidManifest.xml).
+/// On some systems, such as recent versions of Android and iOS, this will
+/// always return false unless the application has been configuration to allow
+/// querying the system for launch support. See
+/// [the README](https://pub.dev/packages/url_launcher#configuration) for
+/// details.
 Future<bool> canLaunch(String urlString) async {
   return await UrlLauncherPlatform.instance.canLaunch(urlString);
 }
diff --git a/packages/url_launcher/url_launcher/pubspec.yaml b/packages/url_launcher/url_launcher/pubspec.yaml
index 3296714..9e7c8b2 100644
--- a/packages/url_launcher/url_launcher/pubspec.yaml
+++ b/packages/url_launcher/url_launcher/pubspec.yaml
@@ -3,7 +3,7 @@
   web, phone, SMS, and email schemes.
 repository: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22
-version: 6.0.14
+version: 6.0.15
 
 environment:
   sdk: ">=2.14.0 <3.0.0"