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

发布于 1周前 作者 sinazl 来自 Flutter

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 类自动处理。

然而,对于除 httphttps 之外的任何方案,你应该使用 query 参数和 encodeQueryParameters 函数来处理任何查询参数,而不是使用 UriqueryParameters 构造函数参数,因为存在一个关于 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 配置:如果 useWebViewtrue,可以通过 webViewConfiguration 参数配置 WebView 的行为,例如是否启用 JavaScript。
  4. 头部信息:通过 headers 参数添加 HTTP 头部信息,这对于某些需要认证的 URL 可能很有用。
  5. 回调处理:通过 onLaunchCompleted 参数处理 URL 启动完成后的回调,可以用来显示成功或失败的提示信息。

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

回到顶部