Flutter - 包(Package)简介
Flutter - 包简介
Section titled “Flutter - 包简介”包(Packages)是 Dart 和 Flutter 中代码共享和组织的基础。它们允许开发者捆绑可重用的库(libraries)、模块(modules)或插件(plugins),可以轻松地集成到多个项目中。
什么是包 (Package)?
Section titled “什么是包 (Package)?”一个 Dart 包本质上是一个目录,包含 Dart 代码和一个关键的 pubspec.yaml 文件,该文件定义了其元数据(名称、版本、依赖项 dependencies 等)。它类似于一个 Dart 应用,但通常没有 main() 入口点,除非它被设计为可执行文件。
标准的包结构约定:
lib/: 包含包的公共代码。lib/内部的代码可以被其他包/应用导入。lib/src/: 包含私有的实现细节。lib/src/内部的代码通常不应由使用者直接导入;相反,应从lib/内部的某个文件导出。lib/<package_name>.dart: 通常用作包的主要入口文件,导出主要的公共 API。pubspec.yaml: 定义包的元数据文件。README.md: 解释包功能和使用方法的文档。CHANGELOG.md: 记录包版本之间变化的文件。LICENSE: 指定软件许可。example/: (可选但推荐)包含一个小型示例应用,演示如何使用该包。
要使用名为 my_package 的包中的代码:
// 导入主库文件import 'package:my_package/my_package.dart';
// 或者导入 lib/ 中的特定文件import 'package:my_package/utils/helpers.dart';根据其依赖项(dependencies)和用途,包可以大致分为以下几类:
Dart 包
Section titled “Dart 包”仅包含纯 Dart 代码,不依赖于 Flutter。它们可以用在任何 Dart 项目中(Flutter 应用、命令行工具 command-line tools、服务器端应用 server-side applications)。示例:http、intl、collection。
Flutter 包
Section titled “Flutter 包”依赖于 Flutter 框架(在 pubspec.yaml 中标记 sdk: flutter),包含专门用于 Flutter 应用的 Dart 代码,通常提供自定义 widget 或 Flutter 特定的工具类。它们不包含平台特定(Android/iOS)的原生代码。
Flutter 插件 (Plugins)
Section titled “Flutter 插件 (Plugins)”一种特殊的 Flutter 包类型,既包含 Dart 代码,也包含平台特定的原生代码(Android 使用 Kotlin/Java,iOS 使用 Swift/Objective-C)。插件用于访问仅通过 Dart 无法获得的功能(例如,相机 camera、GPS、设备传感器 device sensors、原生 SDK)。它们内部使用平台通道(Platform Channels)。示例:url_launcher、camera、shared_preferences、google_maps_flutter。
将第三方包集成到你的 Flutter 项目中非常简单:
- 查找包: 在官方仓库 pub.dev 上发现包:https://pub.dev/。
- 添加依赖项: 打开你的项目中的
pubspec.yaml文件,并在dependencies:部分下添加包,指定包名称和所需的版本约束(例如,^1.2.3允许版本 >= 1.2.3 且 < 2.0.0)。
dependencies: flutter: sdk: flutter # 在此处添加包 http: ^1.1.0 provider: ^6.1.1 intl: ^0.18.1- 安装包: 在你的终端中从项目根目录运行
flutter pub get,或使用 IDE(Android Studio/VS Code 通常在保存pubspec.yaml后自动提示)提供的“Pub get”操作。这将下载并链接该包。 - 导入和使用: 在你的代码中导入包中必要的 Dart 文件,并开始使用其类和函数。
import 'package:http/http.dart' as http; // 使用 'as' 创建命名空间import 'package:provider/provider.dart';
void main() { // 使用包中的函数/类 http.get(...); ChangeNotifierProvider(...);}开发 Flutter 插件
Section titled “开发 Flutter 插件”让我们将平台通道示例中的 my_browser 实现创建为一个可重用的 Flutter 插件。
- 创建插件项目: 使用 IDE 或命令行:
-
- IDE (Android Studio/VS Code):
File(文件)>New(新建)>New Flutter Project...(新建 Flutter 项目…)> 选择Flutter Plugin(Flutter 插件)。填写名称(my_browser)、组织(organization)和所需平台。
- IDE (Android Studio/VS Code):
-
- 命令行: 运行
flutter create --template=plugin --platforms=android,ios -a kotlin -i swift my_browser(标志指定平台和首选原生语言)。
- 命令行: 运行
- 实现 Dart API(
lib/my_browser.dart): 定义插件的公共 Dart 接口。
import 'dart:async';import 'package:flutter/services.dart';
class MyBrowser { // 使用插件名称作为通道名称 static const MethodChannel _channel = MethodChannel('my_browser');
// 可选:模板中通常包含的平台版本示例方法 static Future<String?> get platformVersion async { final String? version = await _channel.invokeMethod('getPlatformVersion'); return version; }
// 定义打开浏览器的方法 Future<bool> openBrowser(String urlString) async { try { final bool? result = await _channel.invokeMethod('openBrowser', { 'url': urlString }); // 返回原生代码的成功状态,错误/null 时默认为 false return result ?? false; } on PlatformException catch (e) { print('Failed to open browser via plugin: ${e.message}'); return false; } catch (e) { print('An unexpected error occurred in openBrowser: $e'); return false; } }}- 实现 Android 原生代码(
android/src/main/kotlin/.../MyBrowserPlugin.kt): 实现平台通道处理程序(platform channel handler)。
package com.example.my_browser // 使用你的插件包名
import androidx.annotation.NonNullimport io.flutter.embedding.engine.plugins.FlutterPluginimport io.flutter.plugin.common.MethodCallimport io.flutter.plugin.common.MethodChannelimport io.flutter.plugin.common.MethodChannel.MethodCallHandlerimport io.flutter.plugin.common.MethodChannel.Resultimport io.flutter.embedding.engine.plugins.activity.ActivityAwareimport io.flutter.embedding.engine.plugins.activity.ActivityPluginBindingimport android.app.Activityimport android.content.Intentimport android.net.Uriimport android.util.Log
/** MyBrowserPlugin */class MyBrowserPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { /// 持有 Flutter 与原生 Android 之间通信的 MethodChannel private lateinit var channel : MethodChannel private var activity: Activity? = null // 持有 activity 引用
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { // 使用 binding 中的 binaryMessenger channel = MethodChannel(flutterPluginBinding.binaryMessenger, "my_browser") channel.setMethodCallHandler(this) }
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { channel.setMethodCallHandler(null) }
// MethodCallHandler 实现 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 && activity != null) { val success = openBrowserInActivity(url) result.success(success) } else if (activity == null) { result.error("NO_ACTIVITY", "Cannot open browser without an activity.", null) } else { result.error("INVALID_ARGUMENT", "URL argument is missing or invalid.", null) } } else { result.notImplemented() } }
private fun openBrowserInActivity(url: String): Boolean { return try { val intent = Intent(Intent.ACTION_VIEW) intent.data = Uri.parse(url) activity?.startActivity(intent) true // 表示成功 } catch (e: Exception) { Log.e("MyBrowserPlugin", "Error opening browser", e) false // 表示失败 } }
// ActivityAware 实现 (获取 context/activity) 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 }}- 实现 iOS 原生代码(
ios/Classes/SwiftMyBrowserPlugin.swift):
import Flutterimport UIKit
public class SwiftMyBrowserPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { // 使用 registrar.messenger() let channel = FlutterMethodChannel(name: "my_browser", binaryMessenger: registrar.messenger()) let instance = SwiftMyBrowserPlugin() registrar.addMethodCallDelegate(instance, channel: channel) }
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { if call.method == "getPlatformVersion" { result("iOS " + UIDevice.current.systemVersion) } else if call.method == "openBrowser" { if let args = call.arguments as? [String: Any], let urlString = args["url"] as? String { openBrowser(urlString: urlString, result: result) } else { result(FlutterError(code: "INVALID_ARGUMENT", message: "URL argument is missing or not a string", details: nil)) } } else { result(FlutterMethodNotImplemented) } }
private func openBrowser(urlString: String, result: @escaping FlutterResult) { guard let url = URL(string: urlString) else { result(FlutterError(code: "INVALID_URL", message: "Could not parse URL: \(urlString)", details: nil)) return }
if UIApplication.shared.canOpenURL(url) { UIApplication.shared.open(url, options: [:]) { (success) in result(success) } } else { result(FlutterError(code: "CANNOT_OPEN_URL", message: "Cannot open URL: \(urlString)", details: nil)) } }}- 测试插件: 生成的插件项目包含一个
example/目录。修改example/lib/main.dart来导入并使用你的插件。
// 在 example/lib/main.dart 中import 'package:flutter/material.dart';import 'dart:async';
import 'package:flutter/services.dart';import 'package:my_browser/my_browser.dart'; // 导入插件
void main() { runApp(const MyApp());}
class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key);
@override State<MyApp> createState() => _MyAppState();}
class _MyAppState extends State<MyApp> { String _platformVersion = 'Unknown'; final _myBrowserPlugin = MyBrowser(); // 创建实例 bool _browserOpened = false;
@override void initState() { super.initState(); initPlatformState(); }
Future<void> initPlatformState() async { String platformVersion; try { platformVersion = await MyBrowser.platformVersion ?? 'Unknown platform version'; } on PlatformException { platformVersion = 'Failed to get platform version.'; } // 检查 widget 是否仍在树中 if (!mounted) return;
setState(() { _platformVersion = platformVersion; }); }
Future<void> _callOpenBrowser() async { bool success = await _myBrowserPlugin.openBrowser('https://flutter.dev'); if (mounted) { // Check if widget is still in the tree setState(() { _browserOpened = success; }); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(success ? 'Browser open attempt successful.' : 'Browser open attempt failed.')), ); } }
@override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('Plugin Example App'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('Running on: $_platformVersion\n'), ElevatedButton( onPressed: _callOpenBrowser, child: const Text('Open Browser (flutter.dev)'), ), if (_browserOpened) const Text('Browser opened! (Check device)'), ], ) ), ), ); }}- 在 Android 和 iOS 模拟器/设备上运行示例应用(
cd example,flutter run)以验证功能。 - 发布(可选): 测试通过后,你可以将你的插件发布到 pub.dev,供他人使用。
开发插件可以通过连接 Dart 和强大的原生平台 API 来扩展 Flutter 的能力。