跳转到内容

宿主集成

Flutter 宿主拥有应用边界。Applet 可以通过动作请求工作,宿主决定是否允许。

Applet.asset(
'src/app.js',
onAction: (action) async {
switch (action.name) {
case 'scanner.open':
await scanner.open(action.payload);
return;
case 'analytics.track':
await analytics.track(action.payload);
return;
default:
throw UnsupportedError('Unknown applet action: ${action.name}');
}
},
);
FilledButton("Scan", {
onPressed: Applet.action("scanner.open", { source: "checkout" }),
});

暴露小而明确的动作:

  • analytics.track;
  • navigator.openProduct;
  • scanner.open;
  • clipboard.copy;
  • auth.refreshSession;
  • download.start.

不要把原始平台通道、任意文件访问、安全存储或网络 client 直接暴露给 JavaScript。

载荷必须可序列化。即使 applet 随 App 内置,也要把每个载荷当作不可信输入。

Future<void> handleAppletAction(AppletAction action) async {
switch (action.name) {
case 'navigator.openProduct':
final payload = action.payload;
if (payload is! Map || payload['id'] is! String) {
throw const FormatException('Expected product id.');
}
await navigator.openProduct(payload['id'] as String);
return;
}
}

约定变化时使用稳定动作名和版本化载荷:

Applet.action("navigator.openProduct", {
version: 1,
id: product.id,
});

使用 loadingBuildererrorBuilder 让宿主 UX 可控:

Applet.asset(
'src/app.js',
loadingBuilder: (context, snapshot) =>
const Center(child: CircularProgressIndicator()),
errorBuilder: (context, error, stackTrace) =>
ErrorScreen(error: error),
);

把宿主动作名称、载荷和结果形状作为 API 约定维护。每个 applet Bundle 应绑定最低宿主版本。

远程 Bundle 加载前先检查 manifest。如果 Bundle 需要更新的宿主 API,显示兜底 页面,并保留最后一个可用版本。