Flutter 是 Google 的免费开源 UI 应用程序开发工具包。它用于使用单个代码库在 Android 和 iOS 上构建高质量的本机界面。Flutter 的一件有趣的事情是它可以与现有代码一起工作,并且被全世界的开发人员和组织使用。在本文中,我们将学习如何使用 Flutter 开发自定义插件。
我们打算开发的 Flutter SDK 也可以在您的 Android 或 iOS 设备上运行。因此,我们想开发一个解决方案,我们可以在其中使用现有的 Android 和 iOS SDK 并开发 Flutter SDK。
所有的头脑风暴最终使我们产生了在 Flutter 中开发自定义插件的想法。自定义插件遵循基于 Flutter 灵活系统的基本机制,允许在 Android 上调用 Kotlin 或 Java 或 iOS 上的 Swift 或 Objective-C 代码中可用的平台特定 API。
跨不同渠道的 Flutter SDK 工作
Flutter 内置的特定于平台的 API 支持不依赖于代码生成,而是依赖于使用平台通道的灵活消息传递样式。要创建自定义插件,让我们详细了解 Flutter 架构:
- 应用程序的 Flutter 部分 通过平台通道向其主机(应用程序的 iOS 或 Android 部分)发送消息。
- 宿主 在平台通道上 监听并接收消息。然后它调用任意数量的特定于平台的 API——使用本机编程语言——并将响应发送回客户端 , 即应用程序的 Flutter 部分,如下所示:
平台通道如何在不同平台之间工作的架构概述
使用 Flutter 构建自定义插件
入门
以下示例演示如何调用特定于平台的 API 来检索和显示当前电池电量。 它通过单一平台消息 使用 Android BatteryManager API 和 iOS API ,.device.batteryLevel``getBatteryLevel()
第 1 步:创建包
要创建插件包,
--template=plugin将标志与 Flutter 命令一起 使用create。- 使用
--platforms=后跟逗号分隔列表的选项来指定插件支持的平台。可用平台包括 Android、iOS、Web、Linux、macOS 和 Windows。 - 使用该
--org选项指定您的组织,使用反向域名表示法。该值用于生成的插件代码中的各种包和包标识符。 - 使用
-a选项指定 Android 的语言或 -i 选项指定 iOS 的语言。 - 下面是为 Android、iOS 平台创建插件包的示例命令,同时为 Android 使用 java,为 iOS 使用 Objective-C。
flutter create
-
此命令在文件夹中创建一个插件项目
batteryLevel,其特定内容如下:lib/batteryLevel.dart -插件的 Dart API。android/src/main/java/com/rudderstack/batteryLevel/BatteryLevelPlugin.java- Java 中插件 API 的特定于 Android 平台的实现。ios/Classes/BatteryLevelPlugin.m- Objective-C 中插件 API 的 iOS 平台特定实现。example/- 依赖于插件并说明如何使用它的 Flutter 应用程序。
在 Flutter 网站上查看平台端如何接收不同的 dart 值,反之亦然。
第二步:创建 Flutter 平台客户端
应用程序的 State 类保存当前应用程序状态。扩展它以保持当前的电池状态。
MethodChannel首先,使用返回电池电量的单一平台方法 构建通道 。- 通道的客户端和主机端通过在通道构造函数中传递的通道名称连接。
注意:单个应用中使用的所有频道名称必须是唯一的。
- 使用唯一 域前缀为频道名称添加前缀。例如,
org.rudderstack.dev/battery。 - 打开
batteryLevel.dart位于lib文件夹中的文件。 - 创建
method通道对象,如下所示,通道名称为org.rudderstack.dev/battery。 - 请确保在 Android 和 iOS 平台上使用与 Flutter 中相同的名称初始化通道对象。
import 'dart:async';
import 'package:flutter/services.dart';
class BatteryLevel { static const MethodChannel _channel = MethodChannel('org.rudderstack.dev/battery');
// Get battery level.}
- 接下来,调用方法通道上的方法,使用字符串标识符指定要调用的具体方法
getBatteryLevel。例如,如果平台不支持平台 API(例如在模拟器中运行时),调用可能会失败。因此,将调用invokeMethod包装在一条try-catch语句中。 - 获得电池电量后,使用以下代码将其返回:
// Get battery level. static Future<String> getBatteryLevel() async { String batteryLevel; try { final int result = await _channel.invokeMethod('getBatteryLevel'); batteryLevel = 'Battery level: $result%.'; } on PlatformException { batteryLevel = 'Failed to get battery level.'; } return batteryLevel; }}
- 现在,替换该
example/lib/main.dart文件以包含一个小的用户界面,该界面以字符串形式显示电池状态和一个用于刷新值的按钮:
import 'package:flutter/material.dart';import 'dart:async';
import 'package:flutter/services.dart';import 'package:batteryLevel/batteryLevel.dart';
void main() { runApp(MaterialApp(home: MyApp()));}
class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState();}
class _MyAppState extends State<MyApp> { String _batteryLevel = 'Unknown';
@override void initState() { super.initState(); }
// Platform messages are asynchronous, so we initialize in an async method. Future<void> _getBatteryLevel() async { String batteryLevel; // Platform messages may fail, so we use a try/catch PlatformException. try { batteryLevel = await BatteryLevel.getBatteryLevel(); } on PlatformException { batteryLevel = 'Failed to get platform version.'; }
// If the widget was removed from the tree while the asynchronous platform // message was in flight, and we want to discard the reply rather than calling // setState to update our non-existent appearance. if (!mounted) return;
setState(() { _batteryLevel = batteryLevel; }); }
@override Widget build(BuildContext context) { return Material( child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ ElevatedButton( child: Text('Get Battery Level'), onPressed: _getBatteryLevel, ), Text(_batteryLevel), ], ), ), ); }}
第 3 步:添加特定于 Android 平台的实现
打开 BatteryLevelPlugin.java 内部 android/src/main/java/com/rudderstack/batteryLevel/ 并进行如下更改:
MethodChannel首先,将object 初始化中的channel名称改成org.rudderstack.dev/battery如下:
@Override public void onAttachedToEngine( @NonNull FlutterPluginBinding flutterPluginBinding ) { channel = new MethodChannel( flutterPluginBinding.getBinaryMessenger(), "org.rudderstack.dev/battery" ); channel.setMethodCallHandler(this); }
- 现在,替换
onMethodCall为下面显示的定义来处理getBatteryLevel调用并响应batteryLevel如下:
@Override public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { if (call.method.equals("getBatteryLevel")) { result.success(99); } else { result.notImplemented(); } }
第 4 步:添加特定于 iOS 平台的实现
打开 BatteryLevelPlugin.m 并 ios/Classes/ 进行以下更改:
FlutterMethodChannel首先,将object 初始化中的channel名称改成org.rudderstack.dev/battery如下:
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar { FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:@"org.rudderstack.dev/battery" binaryMessenger:[registrar messenger]]; BatteryLevelPlugin* instance = [[BatteryLevelPlugin alloc] init]; [registrar addMethodCallDelegate:instance channel:channel];}
handleMethodCall接下来,用下面的定义 替换 方法来处理getBatteryLevel调用并响应batteryLevel如下:
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { if ([@"getBatteryLevel" isEqualToString:call.method]) { result(@(99)); } else { result(FlutterMethodNotImplemented); }}
有了这个,我们已经成功开发了一个自定义插件。现在您可以在任意两个平台(Android 和 iOS)上运行该插件并了解它的工作原理。
发布自定义插件
让我们快速看一下开发自定义插件后需要牢记的一些说明:
- 自定义插件开发完成后,您可以将自定义插件发布到 pub.dev ,方便其他开发者使用。但是,在发布之前,请检查
pubspec.yaml、README.md、CHANGELOG.md和LICENSE文件以确保内容完整和正确。 - 接下来在模式下运行publish命令,
dry-run看看是否都通过了分析:
$ flutter pub publish --dry-run
- 下一步是发布到 pub.dev,但请确保您已准备就绪,因为发布是无法恢复的最后一步:
$ flutter pub 发布
有关发布的更多详细信息,请查看 dart.dev 上的发布文档 。