开发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的开发中也会遇到互操作的问题。稍后会讲到。


小红星闪啊闪
914 声望1.9k 粉丝

时不我待