跳到主要内容
ArcBlock Community

Flutter与JS的优雅共舞:在Flutter应用中无缝集成ArcBlock JS SDK

shenxiuqiang
开发者
buildersdemodocssdktutorial

在移动应用开发的世界里,Flutter 以其卓越的跨平台能力和惊艳的 UI 表现力,赢得了无数开发者的青睐。而 ArcBlock 作为领先的区块链开发平台,其强大的 JS SDK 为开发者构建去中心化应用提供了坚实的基础。当这两种优秀的技术相遇,一个现实的问题摆在了我们面前:如何在 Flutter 项目中,优雅地调用和集成功能丰富的 ArcBlock JS SDK?

官方并未提供 Dart 版本的 SDK,我们是否需要从零开始用 Dart 重写所有加密和通信逻辑?答案是否定的。本文将带你走上一条更高效、更稳妥的探索之路,我们将利用前端生态成熟的工具链,搭建一座连接 Flutter 和 JavaScript 的坚固桥梁。

Chapter 1: 方案选型 - 为何以及如何选择?

在探索之初,我们面临几个选择:

  1. AI 代码翻译:我们能否用 AI 将整个 JS SDK 自动翻译成 Dart?这是一个诱人的想法,但在当前阶段,由于语言范式、加密算法的精确性要求以及复杂的依赖关系,AI 翻译一个完整的、生产级的 SDK 风险极高,极易引入难以察觉的安全漏洞。因此,我们果断放弃了这个方案。
  2. 原生交互方案对比:两条主流的技术路径进入了我们的视野。

方案工作原理优点缺点
JS 运行时<br>(如 flutter_js)将一个轻量级的 JS 引擎(如 V8)直接打包进 App 中执行 JS 代码。性能好,无 WebView 依赖,更轻量。环境不完整。浏览器/Node.js 的核心 API(如 fetch, crypto)缺失,需要手动在 Dart 中实现并注入(Polyfill),工作量巨大。
无头 WebView<br>(如 flutter_inappwebview)在后台启动一个隐藏的 WebView 来执行 JS,利用其完整的浏览器环境。环境完整,JS SDK 开箱即用,兼容性最好。资源消耗略高,但对于非密集计算的场景,完全可以接受。

考虑到 ArcBlock SDK 可能会依赖网络 (fetch) 和加密 (crypto) 等浏览器环境 API,“无头 WebView” 方案无疑是最稳妥、最省力的选择。

然而,为了探索 Flutter 与 JS 交互的边界,并挑战更“原生”的集成方式,本文将选择 flutter_js 方案进行深度实践。我们将直面它最大的挑战——依赖管理和环境 Polyfill,并给出一套完整的解决方案。

Chapter 2: 核心思路 - 创建 JS “桥接器”

flutter_js 需要的是一个单一的 JS 脚本文件,但 ArcBlock SDK 是由多个互相依赖的 NPM 包组成的。我们不可能手动将它们合并。怎么办?

答案是:在 Flutter 项目中内嵌一个迷你的、独立的 JavaScript 项目。

这个 JS 项目的唯一使命就是:利用前端世界无比成熟的打包工具(Webpack),将所有零散的 ArcBlock SDK 模块和它们的依赖,打包(Bundle)成一个 Flutter 可以直接使用的、单一的 arc-sdk.bundle.js 文件。

Chapter 3: 实战演练 - 从零到一构建打包流程

现在,让我们卷起袖子,一步步搭建这个 JS 打包流水线。

3.1 创建 js_bundler 项目

在你的 Flutter 项目 didmail_app 的根目录下,创建一个新文件夹 js_bundler。它将是我们独立的 JS 世界。

3.2 初始化 package.json

在 js_bundler 目录下创建 package.json 文件。它负责管理我们的 JS 依赖和构建脚本。

javascript
{
  "name": "didmail-js-bundler",
  "version": "1.0.0",
  "description": "Bundles ArcBlock JS SDK for Flutter app",
  "private": true,
  "scripts": {
    "build": "webpack --mode=production"
  },
  "dependencies": {
    "@arcblock/did-sdk": "^1.18.29",
    "@arcblock/did-wallet": "^1.18.29"
  },
  "devDependencies": {
    "webpack": "^5.93.0",
    "webpack-cli": "^5.1.4"
  }
}

> 提示:你需要将 dependencies 中的包名和版本替换为你实际需要用到的 ArcBlock SDK。

3.3 配置 webpack.config.js

这是最关键的一步。在 js_bundler 目录下创建 webpack.config.js,它会告诉 Webpack 如何打包,以及把成品放在哪里。

javascript
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    // 魔法发生的地方:将打包好的 bundle 文件直接输出到 Flutter 的 assets 目录!
    path: path.resolve(__dirname, '..', 'assets', 'js'),
    filename: 'arc-sdk.bundle.js',
    // 将我们导出的模块挂载到全局 ArcSDK 对象上,方便 Dart 调用
    library: 'ArcSDK',
    libraryTarget: 'umd',
    globalObject: 'this'
  },
};
3.4 编写 JS 入口文件 index.js

在 js_bundler 内创建 src 文件夹,并在其中创建 index.js。这是我们提供给 Dart 的“API 接口”。

javascript
// 导入你需要的 ArcBlock SDK 功能
const { fromMnemonic, fromSecretKey } = require('@arcblock/did-wallet');

// 导出这些函数,以便 Dart 可以通过全局的 'ArcSDK' 对象访问
module.exports = {
  generateWalletFromMnemonic: (mnemonic) => {
    try {
      const wallet = fromMnemonic(mnemonic);
      // 返回一个可被序列化为 JSON 的普通对象
      return {
        address: wallet.address,
        publicKey: wallet.publicKey,
        privateKey: wallet.secretKey,
        mnemonic: wallet.mnemonic,
      };
    } catch (e) {
      throw new Error(`Failed to generate wallet: ${e.message}`);
    }
  },
  // 在这里导出其他你需要的函数
};
3.5 构建!

现在,打开你的系统终端,执行以下命令:

javascript
# 1. 进入 JS 项目目录
cd didmail_app/js_bundler

# 2. 安装所有 JS 依赖
npm install

# 3. 执行构建
npm run build

完成后,一个新鲜出炉的 arc-sdk.bundle.js 文件就会自动出现在 didmail_app/assets/js/ 目录下!

Chapter 4: Flutter 集成 - 让 Dart 调用 JS

JS 的部分已经准备就绪,让我们回到 Dart 的世界。

4.1 添加 Flutter 依赖
javascript
flutter pub add flutter_js
flutter pub add get_it # 用于服务管理
4.2 创建桥接服务 ArcBlockJsService.dart

这个类将封装所有与 flutter_js 的交互细节。

javascript
import 'package:flutter/services.dart';
import 'package:flutter_js/flutter_js.dart';

class ArcBlockJsService {
  late final JavascriptRuntime _runtime;

  ArcBlockJsService._(this._runtime);

  static Future<ArcBlockJsService> init() async {
    final runtime = getJavascriptRuntime();

    // 注入 console.log,用于调试 JS 代码
    runtime.onMessage('console', (args) {
      print('[JS Console]: ${args.join(' ')}');
      return '';
    });

    // 加载我们打包好的 SDK
    final sdkScript = await rootBundle.loadString('assets/js/arc-sdk.bundle.js');
    await runtime.evaluateAsync(sdkScript);
    
    return ArcBlockJsService._(runtime);
  }

  Future<Map<String, dynamic>> generateWalletFromMnemonic(String mnemonic) async {
    // 注意:JS 代码里调用的是 ArcSDK.generateWalletFromMnemonic
    final jsCode = "ArcSDK.generateWalletFromMnemonic('$mnemonic')";

    final result = await _runtime.evaluateAsync(jsCode);
    if (result.isError) {
      throw Exception('JS execution failed: ${result.stringResult}');
    }
    
    return Map<String, dynamic>.from(result.rawResult);
  }
  
  void dispose() {
    _runtime.dispose();
  }
}
4.3 全局管理与调用

使用 get_it 在 main.dart 启动时初始化我们的服务,然后就可以在任何地方(如 AuthViewModel)方便地调用它了。

初始化 (main.dart):

javascript
// ...
import 'package:didmail_app/core/service_locator.dart';

void main() async {
  // ...
  await setupServiceLocator(); // 在这里初始化所有服务
  runApp(const MyApp());
}

调用 (AuthViewModel.dart):

javascript
// ...
import 'package:didmail_app/core/service_locator.dart';
import 'package:didmail_app/services/arcblock_js_service.dart';

class AuthViewModel extends ChangeNotifier {
  Future<void> createAccount(...) async {
    final arcblockService = sl<ArcBlockJsService>(); // 从服务定位器获取实例
    final wallet = await arcblockService.generateWalletFromMnemonic(mnemonic);
    // ... 后续逻辑
  }
}

Chapter 5: 挑战与展望 - 直面 Polyfill

我们选择的 flutter_js 方案最大的挑战现在浮出水面:如果你的 JS SDK 调用了 fetch 或 crypto.subtle 等浏览器/Node.js 才有的 API,flutter_js 会报错,因为它是一个纯净的 JS 引擎。

这时,你就必须手动为这些 API 提供 Polyfill。例如,要实现 fetch,你需要在 ArcBlockJsService.init() 中,利用 Dart 的 http 包,向 JS 环境中注入一个 fetch 函数。这是一个复杂但可行的过程,它要求你深入理解 Dart 与 JS 的消息传递机制。

结语

我们成功地走完了一条在 Flutter 中集成复杂 JS SDK 的完整路径。通过内嵌 JS 子项目和利用 Webpack 打包的策略,我们解决了 flutter_js 无法直接消费 NPM 生态的根本问题,搭建了一套专业且可维护的开发流程。

这个方案不仅适用于 ArcBlock SDK,更可以推广到任何你想在 Flutter 中使用的、功能强大的 JavaScript 库。它完美地诠释了 Flutter 开放和包容的生态理念——让我们站在巨人的肩膀上,将不同技术栈的优势融合,创造出更精彩的应用。

希望这篇文章能为 ArcBlock 社区的 Flutter 开发者们带来启发。Happy coding

3 条回复

你是好人,我跟你15个月前

我看不懂,但是我大受震撼😂

Shuang Liu15个月前
shenxiuqiang15个月前

实际使用过程发现flutter_js的bug:

https://github.com/abner/flutter_js/issues/176

无法解决,打算转RN了

回复