Skip to main content

让数据随 Flutter 一起飞翔

Dart[^1] + Flutter[^2] 是一种流行的跨平台应用框架。JavaScript 代码可以通过 嵌入式引擎 运行。

¥Dart[^1] + Flutter[^2] is a popular cross-platform app framework. JavaScript code can be run through embedded engines.

SheetJS 是一个用于从电子表格读取和写入数据的 JavaScript 库。

¥SheetJS is a JavaScript library for reading and writing data from spreadsheets.

该演示使用 Dart + Flutter 和 SheetJS 来处理电子表格。我们将探讨如何使用 flutter_js 包运行 JavaScript 代码以及如何在 Dart 代码和特定于平台的 JS 引擎之间传递数据。

¥This demo uses Dart + Flutter and SheetJS to process spreadsheets. We'll explore how to use the flutter_js package to run JavaScript code and how to pass data between Dart code and the platform-specific JS engines.

"演示" 创建一个应用,如下图所示:

¥The "Demo" creates an app that looks like the screenshots below:

iOSAndroid

iOS screenshot

Android screenshot

测试部署

本 demo 在以下环境下进行了测试:

¥This demo was tested in the following environments:

真实设备

¥Real Devices

OS设备DartFlutter日期
安卓 30英伟达盾3.4.33.22.22024-06-09
iOS 15.1iPad Pro3.4.33.22.22024-06-09

模拟器

¥Simulators

OS设备DartFlutter开发平台日期
安卓 34像素 3a3.4.33.22.2darwin-x642024-06-09
iOS 17.5iPhone 15 Pro 最大3.4.33.22.2darwin-x642024-06-09
Android 35像素 3a3.5.03.24.0win11-x642024-08-10
遥测

在开始此演示之前,请手动禁用遥测。在 MacOS 上:

¥Before starting this demo, manually disable telemetry. On MacOS:

dart --disable-telemetry
dart --disable-analytics
flutter config --no-analytics
flutter config --disable-telemetry

集成详情

¥Integration Details

此演示假设你熟悉 Dart 和 Flutter。

¥This demo assumes familiarity with Dart and Flutter.

对于 iOS 和 Android 目标,flutter_js package[^3] 分别封装 JavaScriptCore[^4] 和 QuickJS[^5] 引擎。

¥For the iOS and Android targets, the flutter_js package[^3] wraps JavaScriptCore[^4] and QuickJS[^5] engines respectively.

SheetJS 独立脚本 可以在封装引擎中进行解析和评估。

¥The SheetJS Standalone scripts can be parsed and evaluated in the wrapped engines.

加载 SheetJS

¥Loading SheetJS

添加脚本

¥Adding the scripts

pubspec.yaml 中的 flutter.assets 属性指定资源。假设独立脚本和填充程序放置在 scripts 文件夹中,以下代码片段将脚本加载为资源:

¥The flutter.assets property in pubspec.yaml specifies assets. Assuming the standalone script and shim are placed in the scripts folder, the following snippet loads the scripts as assets:

pubspec.yaml
flutter:
assets:
- scripts/xlsx.full.min.js
- scripts/shim.min.js

加载后,可以使用 rootBundle.loadString 加载内容:

¥Once loaded, the contents can be loaded with rootBundle.loadString:

import 'package:flutter/services.dart' show rootBundle;

String shim = await rootBundle.loadString("scripts/shim.min.js");
String sheetjs = await rootBundle.loadString("scripts/xlsx.full.min.js");

初始化

¥Initialization

强烈建议将引擎添加到 StatefulWidget 状态:

¥It is strongly recommended to add the engine to the state of a StatefulWidget:

import 'package:flutter_js/flutter_js.dart';

class SheetJSFlutterState extends State<SheetJSFlutter> {
late JavascriptRuntime _engine;

void initState() {
_engine = getJavascriptRuntime();
}
}

运行 SheetJS 脚本

¥Running SheetJS Scripts

由于获取资源是异步的,因此建议创建一个封装器 async 函数并顺序等待每个脚本:

¥Since fetching assets is asynchronous, it is recommended to create a wrapper async function and sequentially await each script:

class SheetJSFlutterState extends State<SheetJSFlutter> {
String _version = '0.0.0';
late JavascriptRuntime _engine;

void initState() {
_engine = getJavascriptRuntime();
_initEngine(); // note: this is not `await`-ed
}

Future<void> _initEngine() async {
/* fetch and evaluate the shim */
String shim = await rootBundle.loadString("scripts/shim.min.js");
_engine.evaluate(shim);
/* fetch and evaluate the main script */
String sheetjs = await rootBundle.loadString("scripts/xlsx.full.min.js");
_engine.evaluate(sheetjs);
/* capture the version string */
JsEvalResult vers = _engine.evaluate("XLSX.version");
setState(() => _version = vers.stringResult);
}
}

读取数据

¥Reading data

下图描绘了练习册华尔兹:

¥The following diagram depicts the workbook waltz:

Dart 中最常见的二进制数据类型是 Uint8List。它是 http.Response#bodyBytes 的数据类型和 File#readAsBytes() 的返回类型。

¥The most common binary data type in Dart is Uint8List. It is the data type for http.Response#bodyBytes and the return type of File#readAsBytes().

Flutter JS 连接器不为 Uint8List 数据提供简单的互操作。在解析之前,应使用 base64Encode 将数据转换为 Base64。

¥The Flutter JS connector offers no simple interop for Uint8List data. The data should be converted to Base64 using base64Encode before parsing.

一旦传递到 JS 引擎,SheetJS read 函数 [^6] 就可以读取 Base64 编码的字符串,而 sheet_to_csv 实用函数 [^7] 可以从工作表生成 CSV 字符串。可以将该字符串拉回到 Dart 代码中。

¥Once passed into the JS engine, the SheetJS read function[^6] can read the Base64-encoded string and the sheet_to_csv utility function[^7] can generate a CSV string from a worksheet. This string can be pulled back into Dart code.

csv 包提供了一个特殊的 CsvToListConverter 转换器来生成 List<List<dynamic>>(Dart 的精神上相当于数组的数组)。

¥The csv package provides a special CsvToListConverter converter to generate List<List<dynamic>> (Dart's spiritual equivalent of the array of arrays).

以下代码片段从 Dart Uint8List 生成 List<List<dynamic>>

¥The following snippet generates List<List<dynamic>> from a Dart Uint8List:

import 'dart:convert';
import 'package:csv/csv.dart';

class SheetJSFlutterState extends State<SheetJSFlutter> {
List<List<dynamic>> _data = [];
late JavascriptRuntime _engine;

void _processBytes(Uint8List bytes) {
String base64 = base64Encode(bytes);
JsEvalResult func = _engine.evaluate("""
var wb = XLSX.read('$base64');
XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]]);
""");
String csv = func.stringResult;
setState(() { _data = CsvToListConverter(eol: "\n").convert(csv); });
}
}

演示

¥Demo

  1. 请遵循 Flutter[^8] 的官方 "安装" 说明。

    ¥Follow the official "Install" instructions for Flutter[^8].

运行 flutter doctor 并确认检查了以下项目:

¥Run flutter doctor and confirm the following items are checked:

[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0) [✓] Xcode - develop for iOS and macOS (Xcode 15.4)

(实际版本号可能有所不同)

¥(the actual version numbers may differ)

Installation Notes (click to hide)

第一次运行时,可能会出现 "Android 工具链" 警告:

¥On first run, there may be a warning with "Android toolchain":

[!] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
! Some Android licenses not accepted. To resolve this, run: flutter doctor
--android-licenses

如上所述,修复方法是运行命令:

¥As stated, the fix is to run the command:

flutter doctor --android-licenses

第一次运行时,可能会出现 "Xcode" 警告:

¥On first run, there may be a warning with "Xcode":

[!] Xcode - develop for iOS and macOS (Xcode 15.0.1)
✗ Unable to get list of installed Simulator runtimes.

在 Xcode 中打开 "设置" 面板。在 "平台" 下,单击 "iOS" 旁边的 "得到"。

¥Open "Settings" panel in Xcode. Under "Platforms", click "Get" next to "iOS".

在本地测试中,Android 工具链存在问题:

¥In local testing, there were issues with the Android toolchain:

error: Android sdkmanager not found. Update to the latest Android SDK and ensure that the cmdline-tools are installed to resolve this.

Android Studio 默认不安装 Android SDK Command-Line Tools。必须手动安装。

¥Android Studio does not install Android SDK Command-Line Tools by default. It must be installed manually.

假设已安装命令行工具

¥Assuming the command-line tools are installed

通过切换到 Java 20、安装 Android SDK 33 并回滚到 Android SDK Command-Line Tools (revision: 10.0) 修复了此问题

¥This was fixed by switching to Java 20, installing Android SDK 33, and rolling back to Android SDK Command-Line Tools (revision: 10.0)

如果未安装 Google Chrome,flutter doctor 将显示一个问题:

¥If Google Chrome is not installed, flutter doctor will show an issue:

[✗] Chrome - develop for the web (Cannot find Chrome executable at
/Applications/Google Chrome.app/Contents/MacOS/Google Chrome)
! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.

如果安装了 Chromium,则应手动分配环境变量:

¥If Chromium is installed, the environment variable should be manually assigned:

CHROME_EXECUTABLE 环境变量应设置为 chrome 二进制文件的路径。此路径在发行版和包管理器之间有所不同。

¥The CHROME_EXECUTABLE environment variable should be set to the path to the chrome binary. This path differs between distributions and package managers.

运行 flutter emulators 并查找 android 和(仅在 macOS 上)ios 模拟器。

¥Run flutter emulators and look for android and (on macOS only) ios emulators.

Id                  • Name            • Manufacturer • Platform

Pixel_3a_API_35 • Pixel 3a API 35 • Google • android
  1. 禁用遥测。以下命令已确认有效:

    ¥Disable telemetry. The following commands were confirmed to work:

dart --disable-telemetry
dart --disable-analytics
flutter config --no-analytics

基础项目

¥Base Project

  1. 创建一个新的 Flutter 项目:

    ¥Create a new Flutter project:

flutter create sheetjs_flutter
cd sheetjs_flutter
  1. 启动 Android 模拟器。

    ¥Start the Android emulator.

Details (click to hide)

安卓工作室

¥Android Studio

在 Android Studio 中,单击 "更多操作" > "虚拟设备管理器"。在列表中查找模拟设备,然后单击 ▶ 按钮进行播放。

¥In Android Studio, click "More actions" > "Virtual Device Manager". Look for the emulated device in the list and click the ▶ button to play.

命令行

¥Command Line

列出带有 flutter emulators 的可用模拟器:

¥List the available emulators with flutter emulators:

% flutter emulators
2 available emulators:

apple_ios_simulator • iOS Simulator • Apple • ios
Pixel_3a_API_34 • Pixel 3a API 34 • Google • android
^^^^^^^^^^^^^^^--- the first column is the name

第一列显示应传递给 emulator -avd 的名称。在之前的测试中,名称为 Pixel_3a_API_34,启动命令为:

¥The first column shows the name that should be passed to emulator -avd. In a previous test, the name was Pixel_3a_API_34 and the launch command was:

emulator -avd Pixel_3a_API_34

在 macOS 上,~/Library/Android/sdk/emulator/emulator 二进制文件的典型位置。如果找不到,则将该文件夹添加到 PATH

¥On macOS, ~/Library/Android/sdk/emulator/ is the typical location for the emulator binary. If it cannot be found, add the folder to PATH:

export PATH="$PATH":~/Library/Android/sdk/emulator
emulator -avd Pixel_3a_API_34
  1. 当 Android 模拟器打开时,启动应用:

    ¥While the Android emulator is open, start the application:

flutter run
If emulator is not detected (click to show)

In some test runs, flutter run did not automatically detect the emulator.

Run flutter -v -d sheetjs run and the command will fail. Inspect the output:

Command output
[   +6 ms] No supported devices found with name or id matching 'sheetjs'.
[ ] The following devices were found:
...
[ +26 ms] sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64 • Android 13 (API 33) (emulator)
[ ] macOS (desktop) • macos • darwin-arm64 • macOS 13.5.1 22G90 darwin-arm64
...

Search the output for sheetjs. After that line, search for the emulator list. One of the lines will correspond to the running emulator:

[  +26 ms] sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64  • Android 13 (API 33) (emulator)
^^^^^^^^^^^^^--- the second column is the name

The second column is the device name. Assuming the name is emulator-5554, run:

flutter -v -d emulator-5554 run

应用加载后,停止终端进程并关闭模拟器。

¥Once the app loads, stop the terminal process and close the simulator.

  1. 安装 Flutter/Dart 依赖:

    ¥Install Flutter / Dart dependencies:

flutter pub add http csv flutter_js

该命令可能会在 Windows 中失败,并显示以下消息:

¥The command may fail in Windows with the followimg message:

Building with plugins requires symlink support.

Please enable Developer Mode in your system settings. Run start ms-settings:developers to open settings.

如上所述,必须启用 "开发者模式":

¥As stated, "Developer Mode" must be enabled:

  1. 运行 start ms-settings:developers

    ¥Run start ms-settings:developers

  2. 在面板中,启用 "开发者模式" 并在弹出窗口中单击 "是的"。

    ¥In the panel, enable "Developer Mode" and click "Yes" in the popup.

  3. 重新安装依赖:

    ¥Reinstall dependencies:

flutter pub add http csv flutter_js
  1. 用文本编辑器打开 pubspec.yaml。搜索以 flutter: 开头的行(无空格)并添加高亮的行:

    ¥Open pubspec.yaml with a text editor. Search for the line that starts with flutter: (no whitespace) and add the highlighted lines:

pubspec.yaml
# The following section is specific to Flutter packages.
flutter:
assets:
- scripts/xlsx.full.min.js
- scripts/shim.min.js
  1. 下载依赖到 scripts 文件夹:

    ¥Download dependencies to the scripts folder:

mkdir -p scripts
cd scripts
curl -LO https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js
curl -LO https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/shim.min.js
cd ..

PowerShell curl 与官方 curl 程序不兼容。该命令可能会因参数错误而失败:

¥PowerShell curl is incompatible with the official curl program. The command may fail with a parameter error:

Invoke-WebRequest : A parameter cannot be found that matches parameter name 'LO'.

必须改用 curl.exe

¥curl.exe must be used instead:

mkdir -p scripts
cd scripts
curl.exe -LO https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js
curl.exe -LO https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/shim.min.js
cd ..
  1. 下载 main.dartlib/main.dart

    ¥Download main.dart to lib/main.dart:

curl -L -o lib/main.dart https://xlsx.nodejs.cn/flutter/main.dart

PowerShell curl 与官方 curl 程序不兼容。该命令可能会因参数错误而失败:

¥PowerShell curl is incompatible with the official curl program. The command may fail with a parameter error:

Invoke-WebRequest : A parameter cannot be found that matches parameter name 'L'.

必须改用 curl.exe

¥curl.exe must be used instead:

curl.exe -L -o lib/main.dart https://xlsx.nodejs.cn/flutter/main.dart

安卓

¥Android

  1. 使用与步骤 3 相同的说明启动 Android 模拟器。

    ¥Start the Android emulator using the same instructions as Step 3.

  2. 启动应用:

    ¥Launch the app:

flutter run

该应用获取 https://xlsx.nodejs.cn/pres.numbers,解析数据,将数据转换为数组数组,并将数据渲染在 Flutter Table 小部件中。

¥The app fetches https://xlsx.nodejs.cn/pres.numbers, parses, converts data to an array of arrays, and presents the data in a Flutter Table widget.

If emulator is not detected (click to show)

In some test runs, flutter run did not automatically detect the emulator.

Run flutter -v -d sheetjs run and the command will fail. Inspect the output:

Command output
[   +6 ms] No supported devices found with name or id matching 'sheetjs'.
[ ] The following devices were found:
...
[ +26 ms] sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64 • Android 13 (API 33) (emulator)
[ ] macOS (desktop) • macos • darwin-arm64 • macOS 13.5.1 22G90 darwin-arm64
...

Search the output for sheetjs. After that line, search for the emulator list. One of the lines will correspond to the running emulator:

[  +26 ms] sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64  • Android 13 (API 33) (emulator)
^^^^^^^^^^^^^--- the second column is the name

The second column is the device name. Assuming the name is emulator-5554, run:

flutter -v -d emulator-5554 run

在一些演示运行中,构建失败并出现 Android SDK 错误:

¥In some demo runs, the build failed with an Android SDK error:

│ The plugin flutter_js requires a higher Android SDK version.                      │
│ Fix this issue by adding the following to the file /.../android/app/build.gradle: │
│ android { │
│ defaultConfig { │
│ minSdkVersion 21 │
│ } │
│ } │

通过编辑 android/app/build.gradle 已修复此问题。

¥This was fixed by editing android/app/build.gradle.

搜索 minSdkVersion 应显示以下行:

¥Searching for minSdkVersion should reveal the following line:

android\app\build.gradle
        minSdkVersion flutter.minSdkVersion

flutter.minSdkVersion 应替换为 21

¥flutter.minSdkVersion should be replaced with 21:

android\app\build.gradle
        minSdkVersion 21
  1. 关闭 Android 模拟器。

    ¥Close the Android emulator.

iOS

  1. 启动 iOS 模拟器。

    ¥Start the iOS simulator.

  2. 启动应用:

    ¥Launch the app:

flutter run

该应用获取 https://xlsx.nodejs.cn/pres.numbers,解析数据,将数据转换为数组数组,并将数据渲染在 Flutter Table 小部件中。

¥The app fetches https://xlsx.nodejs.cn/pres.numbers, parses, converts data to an array of arrays, and presents the data in a Flutter Table widget.

安卓设备

¥Android Device

  1. 使用 USB 电缆连接 Android 设备。

    ¥Connect an Android device using a USB cable.

如果设备要求允许 USB 调试,请点击 "允许"。

¥If the device asks to allow USB debugging, tap "Allow".

  1. 验证 flutter 是否可以找到该设备:

    ¥Verify that flutter can find the device:

flutter devices

该列表应包括设备:

¥The list should include the device:

  SheetJS (mobile) • 726272627262726272 • android-arm64 • Android 11 (API 30)
^^^^^^^--- the first column is the name
  1. 构建 APK:

    ¥Build an APK:

flutter build apk --release
  1. 在 Android 设备上安装:

    ¥Install on the Android device:

flutter install

脚本可能会要求一个设备:

¥The script may ask for a device:

[1]: SheetJS (1234567890)
[2]: iPhone 15 Pro Max (12345678-9ABC-DEF0-1234-567890ABCDEF)
[3]: macOS (macos)
[4]: Chrome (chrome)
Please choose one (or "q" to quit):

选择设备对应的编号。

¥Select the number corresponding to the device.

  1. 在设备上启动已安装的 sheetjs_flutter 应用。

    ¥Launch the installed sheetjs_flutter app on the device.

应用可能需要 30 秒才能加载内容。

¥The app may take 30 seconds to load the content.

Android 12[^9] 中的 Dart HTTP 客户端存在已知错误。

¥There are known bugs in the Dart HTTP client in Android 12[^9].

iOS 设备

¥iOS Device

  1. 遵循官方 "部署到物理 iOS 设备" 说明 [^10]

    ¥Follow the official "Deploy to physical iOS devices" instructions[^10]

  2. 连接 iOS 设备并验证 flutter 可以找到该设备:

    ¥Connect the iOS device and verify that flutter can find the device:

flutter devices

该列表应包括设备:

¥The list should include the device:

  SheetPad (mobile) • 00000000-0000000000000000 • ios • iOS 15.1 19B74
^^^^^^^^--- the first column is the name
  1. 在设备上运行程序:

    ¥Run the program on the device:

flutter run -d SheetPad

在调试模式下,"Flutter 工具" 将尝试连接到正在运行的应用。设备将请求权限:

¥In debug mode, "Flutter tools" will attempt to connect to the running app. The device will ask for permission:

"Sheetjs Flutter" 希望查找并连接到本地网络上的设备。

¥"Sheetjs Flutter" would like to find and connect to devices on your local network.

点击 "OK" 继续。

¥Tap "OK" to continue.

上次测试此演示时,构建失败并出现错误:

¥When this demo was last tested, the build failed with an error:

Could not build the precompiled application for the device.
Error (Xcode): No profiles for 'com.example.sheetjsFlutter' were found: Xcode couldn't find any iOS App Development provisioning profiles matching 'com.example.sheetjsFlutter'. Automatic signing is disabled and unable to generate a profile. To enable automatic signing, pass -allowProvisioningUpdates to xcodebuild.

消息包含提示:

¥The message includes a hint:

Verify that the Bundle Identifier in your project is your signing id in Xcode
open ios/Runner.xcworkspace

打开工作区并在导航器中选择 "Runner" 项目。在主窗格中,选择 "签名和能力" 并确保选择了团队。从菜单栏中,选择 "产品" > "运行" 以运行应用。

¥Open the workspace and select the "Runner" project in the Navigator. In the main pane, select "Signing & Capabilities" and ensure a Team is selected. From the menu bar, select "Product" > "Run" to run the app.

如果出现 "不受信任的开发者" 错误,则必须在设备上信任证书。以下步骤已在 iOS 15.1 中得到验证:

¥If there is an "Untrusted Developer" error, the certificate must be trusted on the device. The following steps were verified in iOS 15.1:

  1. 在设备上打开 "设置" 应用

    ¥Open the "Settings" app on the device

在新屏幕的 "APPS FROM DEVELOPER" 部分中,应显示 "Sheetjs Flutter"。如果缺少,请点击屏幕顶部附近的 "<" 按钮并从列表中选择其他证书。

¥In the "APPS FROM DEVELOPER" section of the new screen, "Sheetjs Flutter" should be displayed. If it is missing, tap the "<" button near the top of the screen and select a different certificate from the list.

  1. 选择 "一般的" > "VPN 和设备管理"。

    ¥Select "General" > "VPN & Device Management".

  2. 在 "DEVELOPER APP" 部分中,点击证书 "不受信任"。

    ¥In the "DEVELOPER APP" section, tap the certificate that is "Not Trusted".

  3. 确认 "Sheetjs Flutter" 在列表中后,点击 "相信" 按钮,然后点击弹出窗口中的 "相信"。

    ¥After confirming "Sheetjs Flutter" is in the list, tap the "Trust" button and tap "Trust" in the popup.

[^1]: https://dart.dev/ 是 Dart 编程语言的官方网站。

¥https://dart.dev/ is the official site for the Dart Programming Language.

[^2]: https://flutter.dev/ 是 Flutter 框架的官方网站。

¥https://flutter.dev/ is the official site for the Flutter Framework.

[^3]: flutter_js 托管在 Dart 包存储库上。

¥The flutter_js package is hosted on the Dart package repository.

[^4]: 详细信息请参见 专用 "Swift + JavaScriptCore" 演示

¥See the dedicated "Swift + JavaScriptCore" demo for more details.

[^5]: 详细信息请参见 专用 "C + QuickJS" 演示

¥See the dedicated "C + QuickJS" demo for more details.

[^6]: 见 read 于 "读取文件"

¥See read in "Reading Files"

[^7]: 见 sheet_to_csv 于 "CSV 和文本"

¥See sheet_to_csv in "CSV and Text"

[^8]: 见 Flutter 安装说明

¥See the Flutter Installation Instructions

[^9]: 例如,http 存储库中的问题 836 提到 API 调用可能需要 10 秒以上。这是 Dart + Flutter 中的一个问题。

¥For example, Issue 836 in the http repository mentions that API calls may take 10+ seconds. This is an issue in Dart + Flutter.

[^10]: 请参阅 Flutter 文档中的 "部署到物理 iOS 设备"

¥See "Deploy to physical iOS devices" in the Flutter documentation