2020-05-15 12:19:39
Federated plugins
Federated plugins
Flutter 1.12 提出了 federated plugins 的概念。
官网的大致意思是:
在这之前,一个插件中包含了 Dart 代码,Android 平台代码 和 iOS 平台代码,他们都在一个包中。
federated plugins 的目的是把他们分离成独立的包。
需要3种类型的包:
- platform interface package
各平台需要提供的功能,都以接口的形式声明在了这个包中。 - platform package(s)
特定平台实现接口的包。可以有多个,比如一个用于 Web ,一个用于 Mac OS 。 - app-facing package
是给使用插件的人用的。插件为 app 提供的各种功能在这个包中,插件使用者调用这个包中的方法。 接口为这个包提供一个平台的实现,这个包调用实现中的方法。
使用一个插件,直接用到的就是 app-facing package 。
官网给出了一个 Medium 上的文章 How To Write a Flutter Web Plugin, Part 2。
但是不太容易看懂,可以先看另一篇。Modern Flutter Plugin Development 。
文章中反复强调实现接口的时候要用 extends ,不要用 implements 。
虽然官方提出了这个东西,但是官方插件中的安卓和 iOS 代码还是写在老地方。
因为 federated plugins 的出现,主要是方便为安卓和 iOS 之外的平台添加支持,
比如 Web 和 Mac OS 。
不过挺多插件已经把接口分离出来了。
我的演示
如果把插件示例改成 federated plugins 的话。
可以先简单点。直接在同一个位置放3个文件就好了。
/// 文件 platform_interface.dart
/// 相当于 platform interface package
import 'platform_channel.dart';
abstract class DemoInterface {
static DemoInterface instance = DemoChannel();
Future<String> get platformVersion {
throw UnimplementedError('platformVersion has not been implemented.');
}
}
/// 文件 platform_channel.dart
/// 相当于 platform package
import 'package:flutter/services.dart';
import 'platform_interface.dart';
const MethodChannel _channel = const MethodChannel('platform_channel');
class DemoChannel extends DemoInterface {
static void register() {
DemoInterface.instance = DemoChannel();
}
@override
Future<String> get platformVersion async {
final String version = await _channel.invokeMethod('getPlatformVersion');
return version;
}
}
/// 文件 federated_plugin_demo.dart
/// 相当于 app-facing package
import 'dart:async';
import 'platform_interface.dart';
class DemoPlugin {
static Future<String> get platformVersion async {
final String version = await DemoInterface.instance.platformVersion;
return version;
}
}
/// 在 Flutter app 中的使用
import 'dart:async';
import 'federated_plugin_demo.dart';
Future<void> foo() async {
String platformVersion = await DemoPlugin.platformVersion;
}
platform package 实现了接口。
app-facing package 通过接口类的静态成员 instance 得到接口类的实现,
然后就可以调用接口中的方法。
接口中的静态成员 instance 。
当前 app 运行在哪个平台,就会把它设置成哪个平台的实现。
例如通过 DemoChannel 中的 register()
方法进行设置。
这样 “app-facing package” 就可以调用那个特定平台中的实现了。
上面的例子把 instance 初始化了一个需要 MethodChannel 才能与特定平台通信的实例。
这个初始化其实算是设置了一个默认值。
复杂点的例子
这个例子也就是把上面的例子放在不同的包中。
我这个例子和官方做法不太一样,
是3个独立的包,放在了另一个包中。
最外层的包,是 “app-facing package” ,
里面放了
- platform interface package 是接口
- platform channel 作为 “platform package”,这个包中有Android 和 iOS 平台代码。
- platform register 用来设置接口中的 instance 。
官方的做法是把 “app-facing package” 和 Android,iOS原生平台代码放一起了。
我觉得 channel 算是 “platform package”,
官方把它和 “platform interface package” 放一起了。
官方的做法感觉逻辑比较混乱,我就尝试分开下,加深理解。
要注意 pubspec 文件的使用。
另外我写了一个 “platform register”,
感觉这个应该可以由编译器实现,为哪个平台编译,就把 instance 设置成对应平台的实现。
虽然感觉官方的做法不太符合逻辑,
但是实际写插件的话,还是按照官方的写法比较简单。