经过前面几篇文章中对flutter插件相关技术的介绍,本篇我们从1开始手把手的进行插件代码的编写工作,以实现一个简单的桌面小部件功能。
来吧,效果展示来一波~~
目录:
五.编写简易插件(安卓桌面小部件为例)
1.插件基本情况介绍
1.1.基础功能说明
1.2.实现思路说明
2.核心代码展示
2.1.dart端代码示例
2.2.中间插件端说明
2.3.原生端回传机制
2.4.原生端实现展示
3.注意事项说明
五.编写简易插件(安卓桌面小部件为例)
1.插件基本情况介绍
如效果图所示,由于桌面小部件属于原生特有的功能,所以我们在flutter中想实现添加小部件的功能,只能借助于原生来实现,然后由flutter把需要传递的值传给原生。
1.1.基础功能说明
1.本次我们编写的插件就是一个当用户点击添加插件的时候,flutter通过调用原生安卓的创建插件的入口,通过原生代码生成小部件,同时由flutter传递对应的值给原生,以在原生中正常显示,实现基础功能;
2.当在输入框中输入相应的分享次数和获取金额,执行更新命令后,即可以随时更新小插件的内容;
3.桌面小插件的基础能力
首先桌面小插件自创建后一直处于桌面上,默认处于已打开app的底层,桌面小部件也支持长按删除,以及点击桌面小部件时会自动启动App。
1.2.实现思路说明
如何实现该功能呢,其实可以分为5步,即
1.初始化Dart代码类
首先我们在刚创建好的dart类中创建flutter的基本小组件,包括相关按钮点击执行命令以及初始化和更新数据所需变量的设置等
2.搭建插件代码连通dart和原生
在插件类中,仿照官方获取版本号的方法,分别创建2个方法,一个是创建插件的方法,一个是更新插件数据的方法,并准备好要传递的参数
3.flutter端监听原生端说明
在创建的好的dart类或插件类中提前设置好StreamSubscription的监听逻辑,以实现可以实时监听到后续原生端发送的数据资源,若在插件类中做好向flutter分发数据的功能,若在对应dart类中则直接把相关数据资源赋值给第一步设置的变量,以更新对应的widget,从而实现数据的闭环传递。
4.设置原生安卓端调用
在原生安卓代码中初始化EventChannel.EventSink的事件派发对象和EventChannel.StreamHandler事件派发流,之后在合适的时机用eventSink对象派发对应的数据到第3步中正在进行监听的flutter端。
5.原生端桌面小插件实现
最后在以上完成原生和flutter的数据传递流程之后,在安卓原生端实现自己的原生桌面小插件功能,并暴露接口给第四步,在其回调处进行数据或界面的传递即可。
2.核心代码展示
由于,前面几篇基础篇已经对本篇涉及的MethodChannel和EventChannel对象进行了相对全面的描述,这里是为了以该案例综合运用这些Channel对象,所以以下代码会更多偏向使用技巧,而对一些初始化等细节可能不会过多涉及,详情使用可参考前面几篇文章或者文末的demo案例。
2.1.dart端代码示例
如前面所讲内容所示,在example/lib/main.dart方法类中,即flutter的初始化代码中提前设置初始化dart层类,然后在dart层类的初始化方法中做好基本的监听工作,如下所示为在其initState初始化方法中设置的监听内容为:
// 初始化dart层类(一定要记得),先监听
FlutterDesktopAlert flugin = FlutterDesktopAlert();
FlutterDesktopAlert.listener((args, args2,agrs3) {
print("args=$args-----args2=$args2");
if(args == "initCall"){
setState(() {
_shareCount = args2;
_money = agrs3;
});
}
else if(args == "updateCall") {
setState(() {
_shareCount2 = args2;
_money2 = agrs3;
});
}
});
在合适的时机,如示例所示,分别调用即将编写的插件端方法为
// 创建桌面小插件的方法
String? clickStr;
clickStr = await FlutterDesktopAlert.creatDesktopWidget("100.0", "10");
String? clickStr;
if(firstEC.text.isNotEmpty && secondEC.text.isNotEmpty) {
// 更新桌面小插件的方法
clickStr = await FlutterDesktopAlert.updateDesktopWidget(firstEC.text, secondEC.text);
}
else {
ToastUtils.showToast("请输入内容");
}
2.2.中间插件端说明
在示例程序所示的lib/flutter_desktop_alert.dart的中间插件类中按照flutter端的要求在,其初始化方法中提前设置好监听原生的代码如下:
// 方法1:初始化构造方法设置监听内容(统一分发)
FlutterDesktopAlert(){
_streamSubscription = _eventChannel?.receiveBroadcastStream().listen((event) {
final Map<dynamic,dynamic> map = event;
print("map = $map");
String? key = map["event"];
String? shareCount = map["shareCount"];
String? money = map["money"];
print("handle on ---$key---$money---$shareCount");
if(map["event"] == "initCall"){
callBack!(key,shareCount,money);
}
else if(map["event"] == "updateCall") {
callBack!(key,shareCount,money);
}
},onError: errorEventListen,onDone:doneEventListen,) as StreamSubscription;
// WidgetsBinding.instance!.addObserver(this);
}
当然,在此之前,我们也要初始化好对应的Channel以及保持其key同后面安卓key的一致性,以及编写好flutter端需要原生实现的2个方法,这里就不再多言,仅做提醒。
2.3.原生端回传机制
在原生端的 FlutterDesktopAlertPlugin.java类中同样设置好对应的Channel和EventSink等基础对象后,在合适的时机调用如下eventSink.success方法,即可把这里原生的map对象回传给上面的flutter上层。
if (eventSink != null) {
Map map = new HashMap();
map.put("event","updateCall");
map.put("isSucc","1");
map.put("money",money);
map.put("shareCount",shareCount);
eventSink.success(map);
}
同理,需要注意的是,由于这次桌面小插件中需要获取到flutter传递过来的activity对像,所以我们需要在该java类中遵循如下的ActivityAware接口,
并实现其如下的协议,
@Override
public void onAttachedToActivity(ActivityPluginBinding binding) {
activity = binding.getActivity();
}
@Override
public void onDetachedFromActivityForConfigChanges() {
this.onDetachedFromActivity();
}
@Override
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
this.onAttachedToActivity(binding);
}
@Override
public void onDetachedFromActivity() {
activity = null;
}
2.4.原生端实现展示
关于原生端实现,这里主要说明一些基本的配置工作,如下所示,首先是在 AndroidManifest.xml的清单文件中需要增加一些权限配置,以及activity和service的注册如下
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
另外,至于安卓原生的具体代码实现,则详见文末demo中有对应的代码参考,这里就不再进行相关分析了。
3.注意事项说明
最后我们来聊聊关于本次插件编写中涉及的一些注意实现,尤其是第2条,第1条也很重要,但因为是基础,所以没啥说的。
3.1.key值保持统一
即无论是MethodChannel还是EventChannel,当涉及flutter和原生之间交互时的标识,如本demo中的
static const MethodChannel _channel = MethodChannel('flutter_desktop_alert');
static const EventChannel? _eventChannel = EventChannel("flutter_desktop_alert/event");
这2个key值一定要保持统一,才会顺利无论调起原生,还是原生回传flutter都没问题。
3.2.创建插件监听时注意初始化插件类代码,如下的
// 初始化dart层类(一定要记得),先监听
FlutterDesktopAlert flugin = FlutterDesktopAlert();
不要只知道设置,监听结果忘了初始化,那么插件类中就无法获取到原生的回传,更不用说再由插件端回传给flutter端,那就更不可能了
3.3.建议交互时优先考虑字符串去交互,特别是价格方面的细节
前文有提到,关于flutter同原生的交互特点中,有数据类型的对照关系,但是一般情况下,建议基本数据类型中,优先选择字符串来代替int和double,不然当年设置了比如类似价格的问题,看着像是int,用int接收没问题,但是如果价格看着像是double,用int接收,则会报如下的经典错误:
Another exception was thrown: type 'int' is not a subtype of type 'double'
至此,本篇以一个桌面小插件为例,通过前面flutter的Channel的用法为例,进行了整体的实战,希望该案例可以让我们对flutter同原生交互进行更加深入的认识和理解,继续加油💪🏻
示例代码:Flutter调用安卓桌面小部件实现
插件编写完毕了,不能咱自己用的比较爽,是吧,那么下篇我们将尝试几种方案来把这个插件分享出来,常见的有本地使用,GitHub引用,以及yaml文件引用三种,我们下篇见!
flutter插件基础之调用原生界面和flutter组件互相显示功能(四)
好了,本篇完~