Flutter URL启动与增强功能插件enhanced_url_launcher的使用

Flutter URL启动与增强功能插件enhanced_url_launcher的使用

pub package

enhanced_url_launcher 是一个用于在 Flutter 应用中启动 URL 的插件。

支持情况

Android iOS Linux macOS Web Windows
支持 SDK 16+ 11.0+ Any 10.11+ Any Windows 10+

使用方法

要使用此插件,请将 enhanced_url_launcher 添加为 pubspec.yaml 文件中的依赖项。

示例代码

import 'package:flutter/material.dart';
import 'package:enhanced_url_launcher/enhanced_url_launcher.dart';

final Uri _url = Uri.parse('https://flutter.dev');

void main() => runApp(
      const MaterialApp(
        home: Material(
          child: Center(
            child: ElevatedButton(
              onPressed: _launchUrl,
              child: Text('Show Flutter homepage'),
            ),
          ),
        ),
      ),
    );

Future<void> _launchUrl() async {
  if (!await launchUrl(_url)) {
    throw Exception('Could not launch $_url');
  }
}

配置

iOS

在你的 Info.plist 文件中添加任何通过 canLaunchUrl 传递的 URL 方案作为 <key>LSApplicationQueriesSchemes</key> 的条目,否则它将返回 false。

例如:

<key>LSApplicationQueriesSchemes</key>
<array>
  <string>sms</string>
  <string>tel</string>
</array>

更多详情请参阅 Apple 文档。

Android

在你的 AndroidManifest.xml 中添加任何通过 canLaunchUrl 传递的 URL 方案作为 <queries> 条目,否则它将从 Android 11 (API 30) 或更高版本开始返回 false。必须将 <queries> 元素作为根元素的子元素添加到清单中。

例如:

<!-- 提供 API 级别 30 及以上所需的可见性配置 -->
<queries>
  <!-- 如果您的应用检查短信支持 -->
  <intent>
    <action android:name="android.intent.action.VIEW" />
    <data android:scheme="sms" />
  </intent>
  <!-- 如果您的应用检查通话支持 -->
  <intent>
    <action android:name="android.intent.action.VIEW" />
    <data android:scheme="tel" />
  </intent>
</queries>

更多详情请参阅 Android 文档。

支持的 URL 方案

提供的 URL 直接传递给主机平台进行处理。因此,支持的 URL 方案取决于平台和已安装的应用。

常用方案包括:

Scheme Example Action
https:<URL> https://flutter.dev 在默认浏览器中打开 <URL>
mailto:<email address>?subject=<subject>&body=<body> mailto:smith@example.org?subject=News&body=New%20plugin 在默认邮件应用中创建发送到 <email address> 的邮件
tel:<phone number> tel:+1-555-010-999 使用默认电话应用拨打电话 <phone number>
sms:<phone number> sms:5550101234 使用默认短信应用发送短信到 <phone number>
file:<path> file:/home 使用默认应用关联打开文件或文件夹,仅在桌面平台上支持

更多详情请参阅 iOS 文档 和 Android 文档。

检查支持的方案

如果你需要在运行时知道某个方案是否保证可以工作(例如,根据可用情况调整 UI),你可以使用 canLaunchUrl 进行检查。

但是,canLaunchUrl 即使在某些情况下 launchUrl 会正常工作,也可能会返回 false(例如,在网络应用程序中,或者在移动设备上缺少必要的配置等)。因此,在可以提供回退行为的情况下,最好直接使用 launchUrl 并处理失败情况。例如,一个原本会使用 mailto URL 发送反馈电子邮件的 UI 按钮可以在失败时打开基于网页的反馈表单,而不是禁用按钮如果 canLaunchUrl 返回 false。

编码 URL

URL 必须正确编码,特别是当包含空格或其他特殊字符时。通常这由 Uri 类自动处理。

然而,对于除 http 或 https 之外的任何方案,你应该使用 query 参数和 encodeQueryParameters 函数来处理任何查询参数,而不是使用 Uri 的 queryParameters 构造函数参数,因为存在一个关于 Uri 编码查询参数的 bug。使用 queryParameters 将导致许多情况下空格被转换为 +。

示例代码

String? encodeQueryParameters(Map<String, String> params) {
  return params.entries
      .map((MapEntry<String, String> e) =>
          '${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}')
      .join('&');
}

// ...
final Uri emailLaunchUri = Uri(
  scheme: 'mailto',
  path: 'smith@example.com',
  query: encodeQueryParameters(<String, String>{
    'subject': 'Example Subject & Symbols are allowed!',
  }),
);

launchUrl(emailLaunchUri);

SMS 编码略有不同:

final Uri smsLaunchUri = Uri(
  scheme: 'sms',
  path: '0118 999 881 999 119 7253',
  queryParameters: <String, String>{
    'body': Uri.encodeComponent('Example Subject & Symbols are allowed!'),
  },
);

不由 Uri 处理的 URL

在极少数情况下,你可能需要启动一个主机系统认为有效但无法通过 Uri 表达的 URL。对于这些情况,导入 enhanced_url_launcher_string.dart 后提供了使用字符串的替代 API。

在其他情况下使用这些 API 是强烈不建议的,因为提供无效的 URL 字符串是该插件原始 API 中非常常见的错误来源。

文件方案处理

file: 方案可以在桌面平台上使用:Windows、macOS 和 Linux。

我们建议在调用 launchUrl 之前先检查目录或文件是否存在。

示例代码

final String filePath = testFile.absolute.path;
final Uri uri = Uri.file(filePath);

if (!File(uri.toFilePath()).existsSync()) {
  throw Exception('$uri does not exist!');
}
if (!await launchUrl(uri)) {
  throw Exception('Could not launch $uri');
}

macOS 文件访问配置

如果需要访问应用沙箱外的文件,你需要有必要的 权限。

浏览器与应用内处理

在某些平台上,web URL 可以在应用内网页视图中启动,也可以在默认浏览器中启动。默认行为取决于平台(请参阅 launchUrl 的文档以了解详细信息),但在支持的平台上可以通过传递 LaunchMode 来使用特定模式。

完整示例 Demo

以下是完整的示例代码,展示了如何使用 enhanced_url_launcher 插件的不同功能。

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:enhanced_url_launcher/enhanced_url_launcher.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'URL Launcher',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'URL Launcher'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool _hasCallSupport = false;
  Future<void>? _launched;
  String _phone = '';

  [@override](/user/override)
  void initState() {
    super.initState();
    // 检查电话呼叫支持。
    canLaunchUrl(Uri(scheme: 'tel', path: '123')).then((bool result) {
      setState(() {
        _hasCallSupport = result;
      });
    });
  }

  Future<void> _launchInBrowser(Uri url) async {
    if (!await launchUrl(
      url,
      mode: LaunchMode.externalApplication,
    )) {
      throw Exception('Could not launch $url');
    }
  }

  Future<void> _launchInWebViewOrVC(Uri url) async {
    if (!await launchUrl(
      url,
      mode: LaunchMode.inAppWebView,
      webViewConfiguration: const WebViewConfiguration(
          headers: <String, String>{'my_header_key': 'my_header_value'}),
    )) {
      throw Exception('Could not launch $url');
    }
  }

  Future<void> _launchInWebViewWithoutJavaScript(Uri url) async {
    if (!await launchUrl(
      url,
      mode: LaunchMode.inAppWebView,
      webViewConfiguration: const WebViewConfiguration(enableJavaScript: false),
    )) {
      throw Exception('Could not launch $url');
    }
  }

  Future<void> _launchInWebViewWithoutDomStorage(Uri url) async {
    if (!await launchUrl(
      url,
      mode: LaunchMode.inAppWebView,
      webViewConfiguration: const WebViewConfiguration(enableDomStorage: false),
    )) {
      throw Exception('Could not launch $url');
    }
  }

  Future<void> _launchUniversalLinkIos(Uri url) async {
    final bool nativeAppLaunchSucceeded = await launchUrl(
      url,
      mode: LaunchMode.externalNonBrowserApplication,
    );
    if (!nativeAppLaunchSucceeded) {
      await launchUrl(
        url,
        mode: LaunchMode.inAppWebView,
      );
    }
  }

  Widget _launchStatus(BuildContext context, AsyncSnapshot<void> snapshot) {
    if (snapshot.hasError) {
      return Text('Error: ${snapshot.error}');
    } else {
      return const Text('');
    }
  }

  Future<void> _makePhoneCall(String phoneNumber) async {
    final Uri launchUri = Uri(
      scheme: 'tel',
      path: phoneNumber,
    );
    await launchUrl(launchUri);
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    // 使用此 URL 的按压事件未基于 'canLaunch' 检查
    // 因为假设每个设备都可以启动一个 web URL。
    final Uri toLaunch =
        Uri(scheme: 'https', host: 'www.cylog.org', path: 'headers/');
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: ListView(
        children: <Widget>[
          Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Padding(
                padding: const EdgeInsets.all(16.0),
                child: TextField(
                    onChanged: (String text) => _phone = text,
                    decoration: const InputDecoration(
                        hintText: '输入要拨打的电话号码')),
              ),
              ElevatedButton(
                onPressed: _hasCallSupport
                    ? () => setState(() {
                          _launched = _makePhoneCall(_phone);
                        })
                    : null,
                child: _hasCallSupport
                    ? const Text('拨打手机')
                    : const Text('不支持拨打电话'),
              ),
              Padding(
                padding: const EdgeInsets.all(16.0),
                child: Text(toLaunch.toString()),
              ),
              ElevatedButton(
                onPressed: () => setState(() {
                  _launched = _launchInBrowser(toLaunch);
                }),
                child: const Text('在浏览器中启动'),
              ),
              const Padding(padding: EdgeInsets.all(16.0)),
              ElevatedButton(
                onPressed: () => setState(() {
                  _launched = _launchInWebViewOrVC(toLaunch);
                }),
                child: const Text('在应用中启动'),
              ),
              ElevatedButton(
                onPressed: () => setState(() {
                  _launched = _launchInWebViewWithoutJavaScript(toLaunch);
                }),
                child: const Text('在应用中启动 (JavaScript 关闭)'),
              ),
              ElevatedButton(
                onPressed: () => setState(() {
                  _launched = _launchInWebViewWithoutDomStorage(toLaunch);
                }),
                child: const Text('在应用中启动 (DOM 存储关闭)'),
              ),
              const Padding(padding: EdgeInsets.all(16.0)),
              ElevatedButton(
                onPressed: () => setState(() {
                  _launched = _launchUniversalLinkIos(toLaunch);
                }),
                child: const Text(
                    '在原生应用中启动通用链接,失败时回退到 Safari。(YouTube)'),
              ),
              const Padding(padding: EdgeInsets.all(16.0)),
              ElevatedButton(
                onPressed: () => setState(() {
                  _launched = _launchInWebViewOrVC(toLaunch);
                  Timer(const Duration(seconds: 5), () {
                    closeInAppWebView();
                  });
                }),
                child: const Text('在应用中启动 + 5 秒后关闭'),
              ),
              const Padding(padding: EdgeInsets.all(16.0)),
              Link(
                uri: Uri.parse(
                    'https://pub.dev/documentation/enhanced_url_launcher/latest/link/link-library.html'),
                target: LinkTarget.blank,
                builder: (BuildContext ctx, FollowLink? openLink) {
                  return TextButton.icon(
                    onPressed: openLink,
                    label: const Text('链接小部件文档'),
                    icon: const Icon(Icons.read_more),
                  );
                },
              ),
              const Padding(padding: EdgeInsets.all(16.0)),
              FutureBuilder<void>(future: _launched, builder: _launchStatus),
            ],
          ),
        ],
      ),
    );
  }
}

更多关于Flutter URL启动与增强功能插件enhanced_url_launcher的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter URL启动与增强功能插件enhanced_url_launcher的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在Flutter中,enhanced_url_launcher 是一个增强版的 URL 启动插件,它提供了比官方 url_launcher 插件更丰富的功能和更好的用户体验。下面是一个使用 enhanced_url_launcher 的代码示例,展示了如何启动一个 URL 并处理一些增强功能。

首先,你需要在 pubspec.yaml 文件中添加 enhanced_url_launcher 依赖:

dependencies:
  flutter:
    sdk: flutter
  enhanced_url_launcher: ^x.y.z  # 替换为最新版本号

然后,运行 flutter pub get 来获取依赖。

接下来,在你的 Dart 文件中,你可以这样使用 enhanced_url_launcher:

import 'package:flutter/material.dart';
import 'package:enhanced_url_launcher/enhanced_url_launcher.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Enhanced URL Launcher Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Enhanced URL Launcher Demo'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            try {
              // 配置 EnhancedUrlLauncherOptions
              final options = EnhancedUrlLauncherOptions(
                // 可选:指定启动 URL 的浏览器(如果设备支持)
                browserPackage: 'com.android.chrome', // 仅 Android
                // 可选:是否使用 WebView 打开 URL(如果插件支持且设备允许)
                useWebView: false,
                // 可选:WebView 的配置(如果 useWebView 为 true)
                webViewConfiguration: WebViewConfiguration(
                  // 例如:是否启用 JavaScript
                  javascriptMode: JavascriptMode.unrestricted,
                ),
                // 可选:头部信息,可以用于某些需要认证的 URL
                headers: <String, String>{
                  'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
                },
                // 可选:处理启动 URL 后的回调
                onLaunchCompleted: (bool success, String? errorMessage) {
                  if (success) {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(content: Text('URL launched successfully!')),
                    );
                  } else {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content: Text('Failed to launch URL: $errorMessage'),
                        backgroundColor: Colors.red,
                      ),
                    );
                  }
                },
              );

              // 启动 URL
              await EnhancedUrlLauncher.launchUrl(
                Uri.parse('https://www.example.com'),
                options: options,
              );
            } catch (e) {
              // 处理异常
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(
                  content: Text('Error launching URL: ${e.message}'),
                  backgroundColor: Colors.red,
                ),
              );
            }
          },
          child: Text('Launch URL'),
        ),
      ),
    );
  }
}

在这个示例中,我们展示了如何使用 EnhancedUrlLauncher 来启动一个 URL,并配置了几个增强功能:

  1. 指定浏览器:通过 browserPackage 参数指定要使用的浏览器(仅 Android)。
  2. 使用 WebView:通过 useWebView 参数指定是否使用 WebView 打开 URL。
  3. WebView 配置:如果 useWebView 为 true,可以通过 webViewConfiguration 参数配置 WebView 的行为,例如是否启用 JavaScript。
  4. 头部信息:通过 headers 参数添加 HTTP 头部信息,这对于某些需要认证的 URL 可能很有用。
  5. 回调处理:通过 onLaunchCompleted 参数处理 URL 启动完成后的回调,可以用来显示成功或失败的提示信息。

请注意,实际使用时,你需要根据具体需求调整这些配置参数。

回到顶部