Flutter实战:手把手教你写Flutter Plugin

前言

若是你对移动端有所关注,那么你必定会据说过Flutter。得益于GoogleFlutter一经推出便得受到了普遍关注。不少开发者跃跃欲试,国内部分大厂,诸如美团、闲鱼等团队已经开始了Flutter实践之旅了。笔者也是蹭了一波热度,学习了一下FlutterFlutter虽然真香,但目前社区显然仍是很不健全,像微信SDK、支付宝等第三方SDK都没法在Flutter项目上直接使用。想要使用这些SDK就曲线救国了。
本文并不探讨如何发布一个Flutter Plugin,只谈如何实现Plugin。下面我将以个人开源项目fluwx为例,手把手教你如何写Flutter Plugincss

在2018年GDD上,Flutter分会场演示代码就用到了Fluwx.详情能够戳这里html

什么是Flutter Plugin

Flutter Plugin是一种特殊的包,一个插件包含一个用Dart编写的API定义,结合Android和iOS的平台特定实现,从而达到两者兼容。
日常咱们使用插件能够到这个网站去搜索。java

如何与原生进行通讯?

消息经过platform channels在客户端(UI)和主机(platform)之间传递,以下图所示:
android

通讯机制.png

摘一段官方文档:

在客户端,MethodChannel(API)容许发送与方法调用相对应的消息。 在平台方 面,Android(API)上的MethodChannel和iOS(API)上的FlutterMethodChannel启用接收方法调用并发回结果。 这些类容许您使用很是少的“样板”代码开发平台插件。ios

所谓的客户端是指Flutter层,而平台层面则是对应Android或者iOS。至于究竟怎么使用MethodChannel,我先卖个关子,后面会具体提到。
既然涉及到了Flutter与Android和iOS的通讯问题,那么咱们必定会有如下几个疑问:git

  • MethodChannel传递的数据支持什么类型?
  • Dart数据类型与Android,iOS类型的对应关系是怎样的?

这两个问题的答案一样来自官方文档:github

Dart Android iOS
null null nil (NSNull when nested)
bool java.lang.Boolean NSNumber numberWithBool:
int java.lang.Integer NSNumber numberWithInt:
int if 32 bits not enough java.lang.Long NSNumber numberWithLong:
double java.lang.Double NSNumber numberWithDouble:
String java.lang.String NSString
Uint8List byte[] FlutterStandardTypedData typedDataWithBytes:
Int32List int[] FlutterStandardTypedData typedDataWithInt32:
Int64List long[] FlutterStandardTypedData typedDataWithInt64:
Float64List double[] FlutterStandardTypedData typedDataWithFloat64:
List java.util.ArrayList NSArray
Map java.util.HashMap NSDictionary

至此,咱们对Flutter插件有了一个简单了解,下面咱们将亲自动手写一个插件。objective-c

建立一个Flutter Plugin项目

Android Studio为例(vscode请用命令行):
ruby

image.png
image.png

一路next就好了。
一个Flutter Plugin就建立成功了,项目结构是这样的:
bash

image.png

咱们着重看一下如下三个文件:

  • lib/src/fluwx_class.dart
  • android/src/main/kotlin/com/jarvan/fluwx/FluwxPlugin.kt
  • ios/Classes/FluwxPlugin.m

下面我会继续以Fluwx为例逐一讲解每一个参数的意义。

MethodChannel的定义

首先,打开lib/src/fluwx_class.dart,咱们会发现以下代码:

final MethodChannel _channel = const MethodChannel('com.jarvanmo/fluwx'); 

重点来了,咱们要实现FlutteriOSAndroid的交互就是经过这个MethodChannelMethodChannel就是咱们的信使,负责dart和原生代码通讯。com.jarvanmo/fluwxMethodChannel的名字,flutter经过一个具体的名字能才够在对应平台上找到对应的MethodChannel,从而实现flutter与平台的交互。一样地,咱们在对应的平台上也要注册名为com.jarvanmo/fluwxMethodChannel
Android上是这样的:

class FluwxPlugin() : MethodCallHandler { companion object { @JvmStatic fun registerWith(registrar: Registrar): Unit { val channel = MethodChannel(registrar.messenger(), "com.jarvanmo/fluwx") channel.setMethodCallHandler(FluwxPlugin()) } } } 

再看iOS端:

@implementation FluwxPlugin + (void)registerWithRegistrar:(NSObject <FlutterPluginRegistrar> *)registrar { FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"com.jarvanmo/fluwx" binaryMessenger:[registrar messenger]]; [registrar addMethodCallDelegate:instance channel:channel]; } @end 

经过上面几个步骤,咱们已经完成了Flutter与原生的桥接工做了,咱们继续。

Flutter调用原生并传递数据

只创建桥接显然是不可以知足咱们的需求,咱们要经过Flutter将数据传递到android和iOS上,进而完成微信的注册。上面咱们提供到了MethodChannel支持的数据类型及其对应关系,下面咱们要在Flutter传递一组数据(Map):

static Future register( {String appId, bool doOnIOS: true, doOnAndroid: true, enableMTA: false}) async { return await _channel.invokeMethod("registerApp", { "appId": appId, "iOS": doOnIOS, "android": doOnAndroid, "enableMTA": enableMTA }); } 

register函数的做用是注册微信,其参数的具体意义不做解释。由示例代码能够看到,咱们将传进来的参数从新组装成了Map并传递给了invokeMethod。其中invokeMethod函数第一个参数为函数名称,即registerApp,咱们将在原平生台用到这个名字。第二个参数为要传递给原生的数据。咱们看一下invokeMethod的源码:

Future<dynamic> invokeMethod(String method, [dynamic arguments]) async { //some code } 

颇有趣的是,第二个参数是dynamic的,那么咱们是否能够传递任何数据类型呢?至少语法上是没有错误的,但实际上这是不容许的,只有对应平台的codec支持的类型才能进行传递,也就是上文提到的数据类型对应表,这条规则一样适用于返回值,也就是原生给Flutter传值。请记住这条规定,再也不作赘述。

如何在原生接收Flutter传递过来的数据?

上面咱们将数据经过Flutter传递给了原生,咱们要原生代码里进行接收与处理,先看Android的代码:

override fun onMethodCall(call: MethodCall, result: Result): Unit { if (call.method == "registerApp") { WXAPiHandler.registerApp(call, result) return } } 

call.method是方法名称,咱们要经过方法名称比对完成调用匹配。当call.method == "registerApp"成立时,说明咱们要调用registerApp,从而进行更多的操做。此时可能会有同窗问,如发现call.method不存在怎么办?很简单,咱们能够经过result向Flutter报告一下该方法没实现:

result.notImplemented()

当调用这个方法以后,咱们会在Flutter层收到一个没实现该方法的异常。
iOS端也是大同小异的:

- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { if ([@"registerApp" isEqualToString:call.method]) { [_fluwxWXApiHandler registerApp:call result:result]; return; } } 

若是方法不存在:

result(FlutterMethodNotImplemented);

经过以上步骤咱们已经可以接收到Flutter的调用了,可是咱们的任务还没完成,由于还没取到咱们想要的数据。参数call携带了由Flutter传递过来的数据,在Android中其数据放在call.arguments,其类型为java.lang.Object,与Flutter传递过来数据类型一一对应。若是数据类型是Map,咱们能够经过如下方式取出对应值:

val appId: String? = call.argument("appId") 

iOS同理:

NSString *appId = call.arguments[@"appId"]; 

当咱们取到了appId之后,咱们就能够进行微信注册了,这里不作叙述。
到这里,咱们已经能够完成Flutter调用原生并接收数据,从而完成微信注册。但这样作并不能让咱们满意,缘由有2个:

  • 如何告诉Flutter咱们的处理结果?
  • 用户老是调皮的,如appId是一个空字符串,如何让Flutterr抛出一个异常?
    对于这2个问题,咱们早就发如今接收Flutter调用的时候会传递一个名字result的参数,经过result咱们能够向Flutter打小报告,小报告的有三种形式:
  • success,成功
  • error,遇到错误
  • notImplemented,没实现对应方法
    其中notImplemented,已经说过了。而success故名思义,就是处理成功,能够回调一些数据,也能够不回传,调用很是简单:
result.success(mapOf(
                WechatPluginKeys.PLATFORM to WechatPluginKeys.ANDROID,
                WechatPluginKeys.RESULT to registered
        ))
result(@{fluwxKeyPlatform: fluwxKeyIOS, fluwxKeyResult: @(isWeChatRegistered)}); 

error见名思义,报告错误,当咱们遇到了一些异常须要回调给Flutter时,这个方法就颇有用了。调用这个方法会使Futter抛出一个异常。先看一下在Android上是怎么调用的:

result.error("invalid app id", "are you sure your app id is correct ?", appId) 

第一个参数是errorCode(错误代码,虽然叫Code但倒是一个String),第二个参数是errorMessage(错误信息),第三个details(详情),这个详情就是错误的具体信息了,固然也能够选择不传。
iOS对应代码以下:

result([FlutterError errorWithCode:@"invalid app id" message:@"are you sure your app id is correct ? " details:appId]); 

到目前为止,咱们已经完成了一半工做,已经完成了经过Flutter实现微信注册,但咱们的工做永不止如此,咱们还要完成经过原生调用Flutter,从而实现分享,支付等的回调。

注意:分享一个小坑,在iOS上,空指针有多是nil或者NSNull,坑就在这。若是Flutter传来的String是null,那么在oc中对应的是NSNull,但微信SDK的参数能够为nil,却不能为NSNull。

WXMediaMessage *message = [WXMediaMessage messageWithTitle:(title == (id) [NSNull null]) ? nil : title Description:(description == (id) [NSNull null]) ? nil : description Object:ext MessageExt:(messageExt == (id) [NSNull null]) ? nil : messageExt MessageAction:(messageAction == (id) [NSNull null]) ? nil : messageAction ThumbImage:thumbImage MediaTag:(tagName == (id) [NSNull null]) ? nil : tagName]; 

原生如何调用Flutter

当咱们完成分享时,咱们可能须要将分享结果传回Flutter。有同窗可能会说,上面咱们已经学习了ResultFlutterResult),能够经过result实现啊。但微信的这些回调是异步的,咱们也不可以长期持有Result对象,因此这个时候咱们要在原生中调用Flutter
原理也同样,在原生代码中,咱们也有一个MethodChannel

val channel = MethodChannel(registrar.messenger(), "com.jarvanmo/fluwx") 
FlutterMethodChannel *channel = [FlutterMethodChannel
            methodChannelWithName:@"com.jarvanmo/fluwx" binaryMessenger:[registrar messenger]]; 

当咱们拿到了MethodChannel,咱们就能够搞事情了:

val result = mapOf( errStr to response.errStr, WechatPluginKeys.TRANSACTION to response.transaction, type to response.type, errCode to response.errCode, openId to response.openId, WechatPluginKeys.PLATFORM to WechatPluginKeys.ANDROID ) channel?.invokeMethod("onShareResponse", result) 
NSDictionary *result = @{
                description: messageResp.description == nil ?@"":messageResp.description, errStr: messageResp.errStr == nil ? @"":messageResp.errStr, errCode: @(messageResp.errCode), type: messageResp.type == nil ? @2 :@(messageResp.type), country: messageResp.country== nil ? @"":messageResp.country, lang: messageResp.lang == nil ? @"":messageResp.lang, fluwxKeyPlatform: fluwxKeyIOS }; [methodChannel invokeMethod:@"onShareResponse" arguments:result]; 

原生调用Flutter和Flutter调用原生的方式实际上是同样的,都是经过MethodChannel调用指定名称的方法,并传递数据。那么,Flutter的接受原生调用的方式和原生接收Flutter调用的方式应该也是样的:

final MethodChannel _channel = const MethodChannel('com.jarvanmo/fluwx') ..setMethodCallHandler(_handler); Future<dynamic> _handler(MethodCall methodCall) { if ("onShareResponse" == methodCall.method) { _responseController .add(WeChatResponse(methodCall.arguments, WeChatResponseType.SHARE)); } return Future.value(true); } 

稍微不同的地方就是,在Flutter中,咱们使用到了Stream:

StreamController<WeChatResponse> _responseController =
    new StreamController.broadcast(); Stream<WeChatResponse> get response => _responseController.stream; 

固然了不使用Stream也能够。经过Stream,咱们能够更轻松地监听回调数据变化:

_fluwx.response.listen((data) {
    //do something }); 

至此,咱们已经完成了微信的注册以及微信回调的回传,剩下的工做是否是能够本身完成啦?

总结

经过本文的学习,咱们已经了解了如何亲手编写一个Flutter插件,而且至少掌握如下几点:

  • 建立一个Flutter Plugin项目
  • Flutter调用原生
  • 原生调用Flutter
  • Flutter调用原生的结果处理,如成功,错误等

最后

附上Fluwx。同时,OpenFlutter欢迎各位开源爱好者分享本身的做品,邮箱:jarvan.mo@gmail.com。QQ群:892398530。
版本全部,转载请注明出处

相关文章
相关标签/搜索