Flutter开发之Package和Plugin开发、测试、发布
在flutter中有包
和插件
两个概念。包(Package
)主要指对flutter相关功能的封装,类似于安卓中的插件和iOS中的三方库。而插件(Plugin
)主要指通过插件调用原生的功能,如获取手机基本信息、获取原生的相机等。两者还是存在一定的差别的,Package一般只包含Dart代码,而插件除了包含有Dart外,还会包含有原生的语言,比如安卓中的Java或Kotlin,和iOS中的Objective-c或Swift。Package和Plugin都是为了封装一些基础组件、业务组件等,组件化开发,这样项目中多处用到的活多个项目用到的直接调用即可。
一、 包(Package)
创建Package主要有两种方式,分别为使用命令行创建和使用IDE创建。 命令行创建非常繁琐,需要指定类型、平台、支持的语言、路径等,不便于输入和修改,这里就不做具体的介绍,推荐使用IDE进行创建。
1.1 创建Package
本文使用Android Studio
进行创建,首先需要选择该package使用的flutter sdk。
然后需要输入插件名、文件目录、插件类型(可以选择是Package还是Plugin),支持开发平台及对应的开发语言。
1.2 Package项目目录结构
如图所示,Package项目的目录结构较为简单,主要是lib
目录下的和项目同名类flutter_package_demo.dart
,该类主要是用于在里面暴露出包中的类,以便其它项目调用。
Package中的代码实现放在lib目录下即可,测试代码写在test
目录下的flutter_package_demo_test.dart
中即可。
1.3 Package测试
这里是比较坑的一点,因为Package是个单纯的Dart库,没有安卓和iOS目录,所以不能直接运行该Package进行联调测试。本文提供一个可测试的方法,可在自己的项目中通过path
引入该package,然后可以通过在项目中调用进行联调,有问题对package修改即可。
二、 插件(Plugin)
Plugin(插件)和Package最大的差别在于Plugin为支持跨平台的插件,可以通过插件调用原生安卓和iOS的功能,如获取手机版本信息、调用原生摄像头等,代码包含有Dart、安卓原生代码、iOS原生代码。
2.1 创建Plugin
这里和创建Package类似,不推荐使用终端进行创建,建议使用IDE进行创建,比较直观且易操作。
1、首先打开Android Studio,然后点击File->New->New Flutter Project。
2、进入到选择flutter sdk页面,选择自己本地的flutter,然后点击next。
3、设置plugin,如下图所示。
4、项目目录
如图所示,lib
目录下的文件为具体实现,example
是测试项目,可以对plugin进行测试,iOS
目录下为iOS原生端实现,android
目录下为安卓原生端实现。
【注】示例plugin是使用flutter3.3.5版本创建,不同版本目录结构可能会有细微差别。
2.2 插件plugin定义
- 接口定义
这里先首先介绍下lib目录下的三个文件:
flutter_plugin_demo_platform_interface.dart
文件是定义接口的地方,在该文件只定义方法。如下代码所示,定义了一个新的printHelloWord
方法。
/// 接口定义(主要定义方法,在channel中实现该方法,具体实现为原生实现对应的方法)
abstract class FlutterPluginDemoPlatform extends PlatformInterface {
/// Constructs a FlutterPluginDemoPlatform.
FlutterPluginDemoPlatform() : super(token: _token);
static final Object _token = Object();
static FlutterPluginDemoPlatform _instance = MethodChannelFlutterPluginDemo();
/// The default instance of [FlutterPluginDemoPlatform] to use.
///
/// Defaults to [MethodChannelFlutterPluginDemo].
static FlutterPluginDemoPlatform get instance => _instance;
/// Platform-specific implementations should set this with their own
/// platform-specific class that extends [FlutterPluginDemoPlatform] when
/// they register themselves.
static set instance(FlutterPluginDemoPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
/// 接口方法定义
Future<String?> getPlatformVersion() {
throw UnimplementedError('platformVersion() has not been implemented.');
}
Future<String?> printHelloWorld() {
throw UnimplementedError('printHelloWorld() has not been implemented.');
}
}
- 接口实现
接口实现主要在channel文件中,该文件为interface的子类,负责具体接口的实现,但方法的具体实现依赖于调用原生端的实现。
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'flutter_plugin_demo_platform_interface.dart';
/// An implementation of [FlutterPluginDemoPlatform] that uses method channels.
class MethodChannelFlutterPluginDemo extends FlutterPluginDemoPlatform {
/// The method channel used to interact with the native platform.
@visibleForTesting
final methodChannel = const MethodChannel('flutter_plugin_demo');
@override
Future<String?> getPlatformVersion() async {
final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
return version;
}
@override
Future<String?> printHelloWorld() async {
// TODO: implement printHelloWord
final str = await methodChannel.invokeMethod<String>('printHelloWorld');
returm str;
}
}
以printHelloWorld()
方法为例,在该文件中实现了该方法,在方法的实现为通过methodChannel调用的原生方法。
- 调用
和plugin项目同名的类主要是供外部调用,若某一项目使用了该插件,则调用该插件的方法就是调用的该类中的方法。 这里的实现可以分为两类,一类为使用Dart直接实现,一类为需要依赖于原生端实现,需要调用interface接口类中的方法.
import 'flutter_plugin_demo_platform_interface.dart';
/// 供项目使用该plugin时调用
class FlutterPluginDemo {
Future<String?> getPlatformVersion() {
return FlutterPluginDemoPlatform.instance.getPlatformVersion();
}
void printHelloWorldDart() {
// 直接Dart实现
return print('Dart直接实现:print hello world');
}
Future<String?> printHelloWorld() {
// 调用 FlutterPluginDemoPlatform 接口定义中方法,具体实现在channel子类中,channel中实现通过methodChannel调用原生方法
return FlutterPluginDemoPlatform.instance.printHelloWorld();
}
}
2.3 iOS原生实现
iOS目录主要为iOS原生端实现,这里需要注意一点的是最好是打开example下的iOS进行实现,这样一边实现一边可以进了调试。因项目默认没有pod文件,所以需要对example目录下的iOS进行pod install
。
如图所示,具体实现是在测试项目中的pod中,该本地pod即为plugin中iOS,在这里可以进行iOS端的实现,本文已上述的printHelloWord
为例。
实现:
这里需要注意的是因本文示例选择的是Swift
语言,所以具体实现是在swift类中,若选择的是oc,则实现是在oc类中。其实swift的实现也是通过oc的类调用的swift。
在FlutterPluginDemoPlugin
类的.m类中可以看到调用的为SwiftFlutterPluginDemoPlugin
,具体的实现是在该类中:
@implementation FlutterPluginDemoPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
[SwiftFlutterPluginDemoPlugin registerWithRegistrar:registrar];
}
@end
swift类中实现:
public class SwiftFlutterPluginDemoPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "flutter_plugin_demo", binaryMessenger: registrar.messenger())
let instance = SwiftFlutterPluginDemoPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
if call.method == "printHelloWorld" {
// 实现
result("这是iOS端实现,打印iOS:hello world")
}
}
}
2.4 测试
测试是使用Android Studio
打开example
测试工程进行测试。
测试代码在example->lib->main.dart中写,目录如图所示:
然后在main.dart
中可以通过插件调用获取到原生端的实现。
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
final _flutterPluginDemoPlugin = FlutterPluginDemo();
String _helloWorldStr = "Hello World";
@override
void initState() {
super.initState();
_printHelloWorld();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
String platformVersion;
// Platform messages may fail, so we use a try/catch PlatformException.
// We also handle the message potentially returning null.
try {
platformVersion =
await _flutterPluginDemoPlugin.getPlatformVersion() ?? 'Unknown platform version';
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
}
Future<void> _printHelloWorld() async {
String str;
try {
str =
await _flutterPluginDemoPlugin.printHelloWorld() ?? '未获取到';
} on PlatformException {
str = 'Failed to get hello world.';
}
if (!mounted) return;
setState(() {
_helloWorldStr = str;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Text('Running on: $_helloWorldStr\n'),
),
),
);
}
}
测试模块代码如上所示,到这里就可以run测试工程查看结果了,运行结果如下图所示:
三、发布
发布主要有两种,一种是发布到Flutter的官方,一种是发布到自己公司的私有库中,两者发布步骤大部分都是相同的,在一些地方存在差异,下面对这两种发布方法进行介绍。
3.1 检查四项文件
需要检查下Package或Plugin中是否包含有pubspec.yaml
、README.md
、CHANGELOG.md
、LICENSE
这四个文件,需要检查下完整性。
这四项内容默认创建的时候都会有,若缺少可以在项目终端中运行 flutter create .
进行补全。
(1)pubspec.yaml
该文件主要是引用外部插件,其中name
、description
、version
、homepage
需要填写完整,publish_to
指将该库发布到指定位置,发布到私有库需要,若发布到pub.dev则不需要。
(2)README.md
该文件主要是写插件或包的内容,便于别人查看时告知该库的作用。
(3) CHANGELOG.md
该文件主要是记录插件或包的版本修改记录,说明每个版本的更新内容。
(4) LICENSE
该文件主要是添加许可证书,若有的话可以写
3.2 校验
校验较为简单,打开终端,进入插件或包的路径,然后运行
flutter packages pub publish --dry-run
然后查看运行结果:
警告信息为0,说明没什么问题,可以进行发布。
3.3 发布
接着校验的步骤,还是在终端当前插件或包下,运行命令进行发布,这里需要注意的是这时候最好先科学上网,然后再运行命令进行发布。
flutter packages pub publish
发布到官方和私有库存在区别,若发布到官方,运行命令后会多一步登录谷歌账号校验的步骤,而发布到私有库不需要。
发布到私有库,需要进行私有仓库的搭建,这个网上有很多教程,可以参考下:Flutter私有仓库搭建
到这里对于Flutter中的Package和Plugin从创建、实现到测试,最后到发布全流程都介绍了,若有什么问题可以评论或留言。
转载自:https://juejin.cn/post/7230699871249924155