開發Flutter的Plugin
新建一個plugin項目calendar_plugin:
$ flutter create --template=plugin --platforms=android,ios calendar_plugin
平台指定為Android和iOS,稍後再加一個平台macOS來看看這個流程可以如何操作。後續可以的話再嘗試添加windows和web。
默認的iOS使用的是swift,Android使用的是kotlin,如果需要換objc或者java可以使用arguments -i objc -a java換到你想要的語言。
生成的項目結構:
- android: Android的原生代碼 (Kotlin)。
- example: 一個Flutter的實例項目,用來展示、測試你開發的plugin的。
- ios: iOS本地代碼(Swift)。
- lib: Plugin的Dart代碼。
- test: 測試Plugin。
- CHANGELOG.md: 一個markdown文件,説明發布版本中包含的修改的文檔。
- pubspec.yaml: 它包含了你的Plugin需要滿足的環境等的信息。
- README.md: 給使用這個Plugin的開發者看的幫助文檔。
實現Dart代碼
在生成的lib目錄下生成了兩個文件:
calendar_plugin_method_channel.dartcalendar_plugin_platform_interface.dartcalendar_plugin.dart
在interface文件裏定義一個方法。在channel文件裏實現這個方法,這個方法也是負責和原生代碼通信的。最後在plugin裏暴露這個方法,這個方法就會在其他項目裏引入這個plugin的時候找到這個方法。
-
添加給calendar增加事件的方法:
Future<String?> addEventToCalendar(String eventName) { throw UnimplementedError(); } -
實現這個方法:
@override Future<String?> addEventToCalendar(String eventName) { return methodChannel.invokeMethod<void>('addEventToCalendar'); } -
在plugin文件裏定義這個方法,暴露給調用方。
Future<String?> addEventToCalendar(String eventName) { return CalendarPluginPlatform.instance.addEventToCalendar(eventName); }
以上就是需要在dart這裏處理的代碼,是不是很簡單。
現在添加它們的相關測試。
在test目錄下的calendar_plugin_test.dart文件,其實已經添加好了。有一個示例測試,專門給一個返回系統版本的方法:getPlatformVersion生成好了:
test('getPlatformVersion', () async {
CalendarPlugin calendarPlugin = CalendarPlugin();
MockCalendarPluginPlatform fakePlatform = MockCalendarPluginPlatform();
CalendarPluginPlatform.instance = fakePlatform;
expect(await calendarPlugin.getPlatformVersion(), '42');
});
在正式開始前還得修改一下platform模擬類。
class MockCalendarPluginPlatform
with MockPlatformInterfaceMixin
implements CalendarPluginPlatform {
@override
Future<String?> getPlatformVersion() => Future.value('42');
@override
Future<String?> addEventToCalendar(String eventName) { // 1
return Future.value(eventName);
}
}
在這裏添加addEventToCalendar模擬方法。
在下面增加一個測試給日曆加事件的方法:
test('addEventToCalendar', () async {
CalendarPlugin calendarPlugin = CalendarPlugin();
MockCalendarPluginPlatform fakePlatform = MockCalendarPluginPlatform();
CalendarPluginPlatform.instance = fakePlatform;
expect(
await calendarPlugin.addEventToCalendar('hello world'), 'hello world'); // 2
});
如果成功的給日曆添加了事件,那麼就返回日曆的文字內容。測試就對比這返回的值就好。
實現iOS部分
首先需要配置你的example的Xcode項目,否則打開報錯。執行這個命令:
cd hello/example; flutter build ios --no-codesign --config-only
之後使用xcode打開你的example項目。
在一個遙遠的地方你可以找到需要編輯的插件的swift文件。
打開插件的swift文件CalendarPlugin.swift,你會看到已經生成好的文件:
import Flutter
import UIKit
public class CalendarPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "calendar_plugin", binaryMessenger: registrar.messenger())
let instance = CalendarPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getPlatformVersion": // *
result("iOS " + UIDevice.current.systemVersion)
default:
result(FlutterMethodNotImplemented)
}
}
}
就是在註釋的*這裏,可以看到已經為獲得系統版本號生成好了代碼。類似的,在handle方法裏添加新增的給calendar添加事件的代碼:
case "addEventToCalendar": // *
return null;
在這個case分支裏,就是需要實際在iOS裏添加事件的代碼。這些不必過多關注。需要關注的是如何處理異常情況。比如沒有傳入事件的title、note或者開始、結束日期等。
case "addEventToCalendar":
if call.arguments == nil {
result(FlutterError(code: "Invalid parameters", message: "Invalid parameters", details: nil))
return
}
code和message都是字符串類型的,按照項目的代碼規定寫就可以。details是Any?類型的,按照項目需要想放什麼都隨意。
按照在前面寫的測試,如果成功添加了一個事件則返回這個時間的title。
result(title)
實現Android部分
首先,需要對日曆的寫入權限
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
在CalendarPlugin.kt文件裏有一個onMethodCall方法。還是老樣子存在一個已經實現好的獲取系統版本的方法。
override fun onMethodCall(call: MethodCall, result: Result) {
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else {
result.notImplemented()
}
}
一樣是在判斷方法名,所以我們可以依葫蘆畫瓢加一個分支。
if (call.method == "getPlatformVersion") {
// 略
}
調用開發好的插件
在*example`項目裏調用剛剛開發好了的插件。
在這個項目的lib目錄下只有一個main.dart文件負責調用插件。修改這個文件:
// 增加一個按鈕和一個Text顯示返回的事件title
Center(
child: Text('Added event $_eventName\n'), // 1
),
ElevatedButton(
onPressed: () {
Permission.calendarFullAccess.request().then((status) { // 2
if (status.isGranted) {
debugPrint("calendar full access is granted");
_calendarPlugin
.addEventToCalendar("hello", "hello world")
.then((value) {
debugPrint("ret is $value");
setState(() { // 3
_eventName = value ?? '';
});
});
} else {
debugPrint("calendar full access is denied");
}
});
},
child: const Text("OK")
),
- 一個
Text顯示返回的事件的title - 按鈕,在點擊事件中首先彈出請求用户的日曆權限。這裏使用的是
permission_handler。具體的安裝和配置方式可以參考permission handler的官方文檔。在很多時候Android和iOS都需要處理runtime權限,這個庫非常有用。 - 在成功的返回了事件title之後setState,這樣這個title才會顯示出來。
運行之後就會在日曆中找到添加進去的事件。不過在android上需要先登錄賬户,所以沒有成功添加。
最後
開發plugin是在處理Flutter開發中難免遇到的,如果沒有開源的庫就只能自己開發了。Plugin主要處理有一定的代碼量的時候,如果只是比較簡單的和native互操作的話可以有更加簡單的處理方式。
Web和桌面app的開發中也會遇到互操作的問題。稍後會講到。