博客 / 詳情

返回

Flutter--寫一個原生插件

開發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.dart
  • calendar_plugin_platform_interface.dart
  • calendar_plugin.dart

在interface文件裏定義一個方法。在channel文件裏實現這個方法,這個方法也是負責和原生代碼通信的。最後在plugin裏暴露這個方法,這個方法就會在其他項目裏引入這個plugin的時候找到這個方法。

  1. 添加給calendar增加事件的方法:

      Future<String?> addEventToCalendar(String eventName) {
     throw UnimplementedError();
      }
  2. 實現這個方法:

      @override
      Future<String?> addEventToCalendar(String eventName) {
     return methodChannel.invokeMethod<void>('addEventToCalendar');
      }
  3. 在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文件的位置

打開插件的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
    }

codemessage都是字符串類型的,按照項目的代碼規定寫就可以。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")
  ),
  1. 一個Text顯示返回的事件的title
  2. 按鈕,在點擊事件中首先彈出請求用户的日曆權限。這裏使用的是permission_handler。具體的安裝和配置方式可以參考permission handler的官方文檔。在很多時候Android和iOS都需要處理runtime權限,這個庫非常有用。
  3. 在成功的返回了事件title之後setState,這樣這個title才會顯示出來。

運行之後就會在日曆中找到添加進去的事件。不過在android上需要先登錄賬户,所以沒有成功添加。

最後

開發plugin是在處理Flutter開發中難免遇到的,如果沒有開源的庫就只能自己開發了。Plugin主要處理有一定的代碼量的時候,如果只是比較簡單的和native互操作的話可以有更加簡單的處理方式。

Web和桌面app的開發中也會遇到互操作的問題。稍後會講到。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.