Kotlin/Native 用KMM写Flutter插件 一、用KMM写Flutter插件 Google官方有一个写Flutter例子How to write a Flutter plugin ,这里把Google plugin_codelab 例子改成用KMM写Flutter插件。
二、如何运行 Android: run shared/plugin_codelab/example/android
iOS:
1、build shared.framework
1 2 use ./gradlew releaseIOSFramework or use new version Android Studio sync
2、run shared/plugin_codelab/example/ios
Tips: before run,shared/build/cocoapods/framework/shared.framework should be generated. The shared.h header file shared/build/cocoapods/framework/shared.framework/Headers/shared.h is generated.
三、设计思路 Android/iOS插件PluginCodelabPlugin只需要实现KMM Module的接口,不写任何逻辑,把逻辑通过接口放在KMM Module中。
1、定义接口中间层用于转发数据 如参考Flutter插件的MethodCall、MethodChannel,定义CommonMethodCall数据类、CommonMethodChannel.Result接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 data class CommonMethodCall( val method: String, val arguments: Any?, ) class CommonMethodChannel { interface Result { fun success(result: Any?) fun error(errorCode: String?, errorMessage: String?, errorDetails: Any?) fun notImplemented() } }
2、在KMM中的commonMain实现CommonCodelabPlugin插件的公共逻辑 CommonCodelabPlugin需要初始化并启动synth?.start(),处理getPlatformVersion、onKeyDown、onKeyUp逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class CommonCodelabPlugin { private val synth = Synth() init { synth?.start() } fun onMethodCall(call: CommonMethodCall, result: CommonMethodChannel.Result) { when (call.method) { "getPlatformVersion" -> { result.success(Platform().platform) } "onKeyDown" -> { try { val arguments = call.arguments as List<*> val numKeysDown = synth?.keyDown((arguments[0] as Int)) result.success(numKeysDown) } catch (ex: Exception) { result.error("1", ex.message, ex.cause) } } "onKeyUp" -> { try { val arguments = call.arguments as List<*> val numKeysDown = synth?.keyUp((arguments[0] as Int)) result.success(numKeysDown) } catch (ex: Exception) { result.error("1", ex.message, ex.cause) } } else -> { result.notImplemented() } } } }
还有包括插件名称也属于公共逻辑
1 2 // 插件Channel名称 const val PLUGIN_CODE_LAB_CHANNEL = "plugin_codelab"
3、实现平台差异特性 这里只列出expect接口,具体实现平台差异特性类请查看源码
1 2 3 4 5 6 7 8 9 10 11 expect class Synth() { fun start() fun keyDown(key: Int): Int fun keyUp(key: Int): Int } expect class Platform() { val platform: String }
4、Android Flutter实现插件KMM接口 Android Flutter实现插件KMM接口,注意这里只实现接口用于中转Flutter与Android/iOS 数据,不能有任何业务逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class PluginCodelabPlugin : FlutterPlugin, MethodCallHandler { private var channel: MethodChannel? = null private var commonCodelabPlugin: CommonCodelabPlugin? = null override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { setup(this, flutterPluginBinding.binaryMessenger) } override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { commonCodelabPlugin?.onMethodCall( call = CommonMethodCall(call.method, call.arguments), result = object : CommonMethodChannel.Result { override fun success(successResult: Any?) { result.success(successResult) } override fun error(errorCode: String?, errorMessage: String?, errorDetails: Any?) { result.error(errorCode, errorMessage, errorDetails) } override fun notImplemented() { result.notImplemented() } }) } override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { channel?.setMethodCallHandler(null) } companion object { private fun setup(plugin: PluginCodelabPlugin, binaryMessenger: BinaryMessenger) { plugin.channel = MethodChannel(binaryMessenger, PLUGIN_CODE_LAB_CHANNEL) plugin.channel?.setMethodCallHandler(plugin) plugin.commonCodelabPlugin = CommonCodelabPlugin() } } }
5、iOS Flutter实现插件KMM接口 Android Flutter实现插件KMM接口,注意这里只实现接口用于中转Flutter与Android/iOS 数据,不能有任何业务逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #import "PluginCodelabPlugin.h" @implementation PluginCodelabPlugin{ int _numKeysDown; FlutterResult _flutterResult; SharedCommonCodelabPlugin* _codelabPlugin; } - (instancetype)init { self = [super init]; if (self) { // create music _codelabPlugin = [[SharedCommonCodelabPlugin alloc] init]; } return self; } - (void)dealloc { // destroy music } + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar { FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName: SharedPluginCodeLabKt.PLUGIN_CODE_LAB_CHANNEL binaryMessenger:[registrar messenger]]; PluginCodelabPlugin* instance = [[PluginCodelabPlugin alloc] init]; [registrar addMethodCallDelegate:instance channel:channel]; } - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { SharedCommonMethodCall *methodCall = [[SharedCommonMethodCall alloc] initWithMethod:call.method arguments:call.arguments]; _flutterResult = result; [_codelabPlugin onMethodCallCall:methodCall result:self ]; } - (void)errorErrorCode:(NSString * _Nullable)errorCode errorMessage:(NSString * _Nullable)errorMessage errorDetails:(id _Nullable)errorDetails { NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:errorCode.intValue userInfo:@{@"errorMessage":errorMessage, @"errorDetails":errorDetails}]; if (_flutterResult) { _flutterResult(error); } } - (void)notImplemented { if (_flutterResult) { _flutterResult(FlutterMethodNotImplemented); } } - (void)successResult:(id _Nullable)result { if (_flutterResult) { _flutterResult(result); } } @end
到这里,已经完成了使用KMM开发一个Flutter插件。使用KMM开发插件的好处是公共逻辑都使用kotlin写,一般公共逻辑比较简单适合使用kotlin写,便于维护。而且,实现了KMM写插件,Flutter写UI。
四、参考链接 Github项目地址:kmm-flutter-plugin