Skip to content

flutter_using_packages

Flutter 的能力通过其丰富的包(packages)生态系统得到了显著增强。包是共享代码的捆绑,类似于其他语言中的库或模块,由 Flutter 社区和 Google 创建。它们使你能够轻松地向应用添加预构建的功能,从而节省开发时间和精力。

Dart 和 Flutter 包的中心仓库是 pub.dev。你可以在其中搜索包、查看其文档、检查其流行度和健康评分,以及查找安装说明。

包通常分为以下几类:

  • Dart 包: 完全用 Dart 编写,包含实用函数、类或业务逻辑。它们不依赖于 Flutter 框架,通常也可用于非 Flutter 的 Dart 项目。示例:http (用于发起 HTTP 请求), intl (用于国际化)。
  • Flutter 包(或插件包 Plugin Packages): 这些包依赖于 Flutter 框架,可能包含 Dart 代码以及平台特定的原生代码(Android/iOS/Web/Desktop)以访问平台特性。它们通常提供 Flutter 组件(Widgets)或访问设备能力。
  • - **插件(Plugins):** 专门包含使用平台通道(platform channels)实现平台特定功能的包。示例:`camera` (访问设备相机), `shared_preferences` (访问平台的简单存储), `webview_flutter` (嵌入 Web 视图)。
  • - **纯 Flutter 包(Pure Flutter Packages):** 这些包依赖于 Flutter 但只包含 Dart 代码,通常提供自定义组件、状态管理解决方案或 Flutter 特定的实用工具。示例:`provider` (状态管理), `cached_network_image` (高级图像加载)。

向你的 Flutter 项目添加包包括以下步骤:

  • 打开你项目的 pubspec.yaml 文件。
  • 在 dependencies: 部分下,添加包名称和所需的版本约束。你可以在 pub.dev 上找到最新版本和推荐的约束。
  • 常见的版本约束:
  • - `^version`:允许更新到新的次要版本和补丁版本,但不允许主要版本(例如,`^2.3.0` 允许 `2.3.1`、`2.4.0`,但不允许 `3.0.0`)。这是最常见和推荐的约束。
  • - `version`:锁定到精确版本。
  • - `any`:允许任何版本(谨慎使用)。
  • 在终端中,从你的项目根目录运行:

  • 大多数 IDE(如 VS Code, Android Studio)会自动检测 pubspec.yaml 的更改,并提示你运行 flutter pub get 或自动运行。

  • 在你想要使用包的 Dart 文件中,在顶部添加 import 语句,使用 package: 指令:
  • 你现在可以使用导入包提供的类、函数和组件了。

你也可以创建自己的包,以便在项目之间共享代码或贡献给社区。

  • 使用 Flutter CLI:

  • 这将创建一个标准的包结构:

    • lib/:包含公共 Dart 代码。
    • lib/src/:按照惯例用于私有实现代码。
    • pubspec.yaml:定义包。
    • README.md, CHANGELOG.md, LICENSE:文档和许可。
    • example/:一个可选的示例 Flutter 应用,演示如何使用该包。
    • android/, ios/ 等(对于插件):包含原生平台代码。

开发插件包括:

  1. 在 lib/ 文件夹中定义 Dart API。
  2. 使用平台通道(MethodChannel, EventChannel)在 Dart 中实现通信逻辑。
  3. 在 Android (Kotlin/Java) 和/或 iOS (Swift/Objective-C) 上实现对应的原生部分,它们监听平台通道,执行原生代码,并将结果发送回。

让我们根据原始教程内容完善浏览器插件示例,假设我们创建了一个名为 my_browser 的插件。

import 'dart:async';
import 'package:flutter/services.dart';
class MyBrowser {
// 定义一个一致的通道名称
static const MethodChannel _channel = MethodChannel('com.example.my_browser/channel');
// 打开浏览器的函数
Future<void> openBrowser(String urlString) async {
try {
// 在原生端调用 'openBrowser' 方法
await _channel.invokeMethod('openBrowser', {'url': urlString});
} on PlatformException catch (e) {
print("Failed to open browser: '${e.message}'."); // 打开浏览器失败:'${e.message}'。
// 考虑重新抛出或适当地处理错误
}
}
// 示例:平台版本(通常包含在模板中)
static Future<String?> get platformVersion async {
final String? version = await _channel.invokeMethod('getPlatformVersion');
return version;
}
}

Android 实现 (android/src/.../MyBrowserPlugin.kt)

Section titled “Android 实现 (android/src/.../MyBrowserPlugin.kt)”
package com.example.my_browser // 调整包名
import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
// 必需的导入
import android.app.Activity
import android.content.Intent
import android.net.Uri
class MyBrowserPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
private lateinit var channel : MethodChannel
private var activity: Activity? = null // 持有 Activity 引用
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.example.my_browser/channel")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else if (call.method == "openBrowser") {
val url = call.argument<String>("url")
if (url != null) {
openBrowser(url, result)
} else {
result.error("INVALID_ARGUMENT", "URL cannot be null", null)
}
} else {
result.notImplemented()
}
}
private fun openBrowser(url: String, result: Result) {
if (activity == null) {
result.error("NO_ACTIVITY", "Cannot open browser without a foreground activity.", null) // 没有前景 Activity,无法打开浏览器。
return
}
try {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(url)
activity?.startActivity(intent)
result.success(true) // 表示成功
} catch (e: Exception) {
result.error("FAILED", "Could not open browser: ${e.message}", null) // 无法打开浏览器:${e.message}
}
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
// ActivityAware 方法
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onDetachedFromActivityForConfigChanges() {
activity = null
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onDetachedFromActivity() {
activity = null
}
}

注意: Android 实现使用了新的 FlutterPlugin 和 ActivityAware 接口,与旧的 Registrar 方法相比,具有更好的生命周期管理。

iOS 实现 (ios/Classes/MyBrowserPlugin.swift)

Section titled “iOS 实现 (ios/Classes/MyBrowserPlugin.swift)”

iOS 实现将类似于平台通道章节中所示的示例,但会适应插件结构。

要在本地使用你正在开发的包(在发布之前),可以在使用该包的应用的 pubspec.yaml 文件中通过路径依赖来引用它:

dependencies:
flutter:
sdk: flutter
my_browser: # 你的本地包名称
path: ../my_browser # 包目录的相对路径

包是高效 Flutter 开发的基础,它利用社区贡献并实现了代码复用。

在将包添加到项目之前,务必检查包的许可协议,并评估其维护状态和兼容性。