576 lines
16 KiB
Markdown
576 lines
16 KiB
Markdown
---
|
|
title: Let Data Fly with Flutter
|
|
sidebar_label: Dart + Flutter
|
|
description: Build data-intensive mobile apps with Dart + Flutter. Seamlessly integrate spreadsheets into your app using SheetJS. Securely process and generate Excel files on the go.
|
|
pagination_prev: demos/static/index
|
|
pagination_next: demos/desktop/index
|
|
sidebar_position: 6
|
|
sidebar_custom_props:
|
|
summary: Dart + JS Interop
|
|
---
|
|
|
|
import current from '/version.js';
|
|
import CodeBlock from '@theme/CodeBlock';
|
|
|
|
Dart[^1] + Flutter[^2] is a popular cross-platform app framework. JavaScript
|
|
code can be run through [embedded engines](/docs/demos/engines).
|
|
|
|
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
|
|
data from spreadsheets.
|
|
|
|
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:
|
|
|
|
<table><thead><tr>
|
|
<th><a href="#demo">iOS</a></th>
|
|
<th><a href="#demo">Android</a></th>
|
|
</tr></thead><tbody><tr><td>
|
|
|
|
![iOS screenshot](pathname:///flutter/ios.png)
|
|
|
|
</td><td>
|
|
|
|
![Android screenshot](pathname:///flutter/and.png)
|
|
|
|
</td></tr></tbody></table>
|
|
|
|
:::note Tested Deployments
|
|
|
|
This demo was tested in the following environments:
|
|
|
|
| OS | Type | Device | Dart | Flutter | Date |
|
|
|:-----------|:-----|:------------------|:--------|:---------|:-----------|
|
|
| Android 34 | Sim | Pixel 3a | `3.2.2` | `3.16.2` | 2023-12-04 |
|
|
| iOS 17.0.1 | Sim | iPhone 15 Pro Max | `3.2.2` | `3.16.2` | 2023-12-04 |
|
|
| Android 29 | Real | NVIDIA Shield | `3.2.2` | `3.16.2` | 2023-12-04 |
|
|
| iOS 15.1 | Real | iPad Pro | `3.2.2` | `3.16.2` | 2023-12-04 |
|
|
|
|
:::
|
|
|
|
:::warning Telemetry
|
|
|
|
Before starting this demo, manually disable telemetry. On MacOS:
|
|
|
|
```bash
|
|
dart --disable-telemetry
|
|
dart --disable-analytics
|
|
flutter config --no-analytics
|
|
flutter config --disable-telemetry
|
|
```
|
|
|
|
:::
|
|
|
|
## Integration Details
|
|
|
|
:::note pass
|
|
|
|
This demo assumes familiarity with Dart and Flutter.
|
|
|
|
:::
|
|
|
|
For the iOS and Android targets, the `flutter_js` package[^3] wraps JavaScriptCore[^4]
|
|
and QuickJS[^5] engines respectively.
|
|
|
|
The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone)
|
|
can be parsed and evaluated in the wrapped engines.
|
|
|
|
### Loading SheetJS
|
|
|
|
#### Adding the 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:
|
|
|
|
```yaml title="pubspec.yaml"
|
|
flutter:
|
|
assets:
|
|
- scripts/xlsx.full.min.js
|
|
- scripts/shim.min.js
|
|
```
|
|
|
|
Once loaded, the contents can be loaded with `rootBundle.loadString`:
|
|
|
|
```dart
|
|
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
|
|
|
|
It is strongly recommended to add the engine to the state of a `StatefulWidget`:
|
|
|
|
```dart
|
|
import 'package:flutter_js/flutter_js.dart';
|
|
|
|
class SheetJSFlutterState extends State<SheetJSFlutter> {
|
|
// highlight-next-line
|
|
late JavascriptRuntime _engine;
|
|
|
|
@override void initState() {
|
|
// highlight-next-line
|
|
_engine = getJavascriptRuntime();
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Running SheetJS Scripts
|
|
|
|
Since fetching assets is asynchronous, it is recommended to create a wrapper
|
|
`async` function and sequentially await each script:
|
|
|
|
```dart
|
|
class SheetJSFlutterState extends State<SheetJSFlutter> {
|
|
String _version = '0.0.0';
|
|
late JavascriptRuntime _engine;
|
|
|
|
@override 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);
|
|
// highlight-start
|
|
/* fetch and evaluate the main script */
|
|
String sheetjs = await rootBundle.loadString("scripts/xlsx.full.min.js");
|
|
_engine.evaluate(sheetjs);
|
|
// highlight-end
|
|
/* capture the version string */
|
|
JsEvalResult vers = _engine.evaluate("XLSX.version");
|
|
setState(() => _version = vers.stringResult);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Reading data
|
|
|
|
The following diagram depicts the workbook waltz:
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
file[(file data\nUint8List)]
|
|
subgraph SheetJS operations
|
|
base64(Base64\nstring)
|
|
wb((SheetJS\nWorkbook))
|
|
csv(CSV\nstring)
|
|
end
|
|
lld(List of\nLists)
|
|
tbl{{Data\nTable}}
|
|
file --> |`base64Encode`\nDart| base64
|
|
base64 --> |`XLSX.read`\nParse Bytes| wb
|
|
wb --> |`sheet_to_csv`\nExtract Data| csv
|
|
csv --> |`CsvToListConverter`\nDart| lld
|
|
lld --> |`Table`\nFlutter| tbl
|
|
```
|
|
|
|
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()`.
|
|
|
|
The Flutter JS connector offers no simple interop for `Uint8List` data. The data
|
|
should be converted to Base64 using `base64Encode` before parsing.
|
|
|
|
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.
|
|
|
|
The `csv` package provides a special `CsvToListConverter` converter to generate
|
|
`List<List<dynamic>>` (Dart's spiritual equivalent of the array of arrays).
|
|
|
|
The following snippet generates `List<List<dynamic>>` from a Dart `Uint8List`:
|
|
|
|
```dart
|
|
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
|
|
|
|
0) Follow the official "Install" instructions for Flutter[^8].
|
|
|
|
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.0.1)
|
|
[✓] Android Studio (version 2022.3)
|
|
```
|
|
|
|
(the actual version numbers may differ)
|
|
|
|
<details open><summary><b>Installation Notes</b> (click to hide)</summary>
|
|
|
|
:::note pass
|
|
|
|
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:
|
|
|
|
```bash
|
|
flutter doctor --android-licenses
|
|
```
|
|
|
|
:::
|
|
|
|
:::note pass
|
|
|
|
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.
|
|
```
|
|
|
|
Open "Settings" panel in Xcode. Under "Platforms", click "Get" next to "iOS".
|
|
|
|
:::
|
|
|
|
:::caution pass
|
|
|
|
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.
|
|
```
|
|
|
|
This was fixed by switching to Java 20, installing `Android SDK 33`, and rolling
|
|
back to `Android SDK Command-Line Tools (revision: 10.0)`
|
|
|
|
:::
|
|
|
|
:::note pass
|
|
|
|
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.
|
|
```
|
|
|
|
If Chromium is installed, the environment variable should be manually assigned:
|
|
|
|
```bash
|
|
export CHROME_EXECUTABLE=/Applications/Chromium.app/Contents/MacOS/Chromium
|
|
```
|
|
|
|
:::
|
|
|
|
</details>
|
|
|
|
Run `flutter emulators` and check for both `ios` and `android` emulators:
|
|
|
|
```
|
|
apple_ios_simulator • iOS Simulator • Apple • ios
|
|
Pixel_3a_API_34 • Pixel 3a API 34 • Google • android
|
|
```
|
|
|
|
1) Disable telemetry.
|
|
|
|
```bash
|
|
dart --disable-telemetry
|
|
dart --disable-analytics
|
|
flutter config --no-analytics
|
|
flutter config --disable-telemetry
|
|
```
|
|
|
|
### Base Project
|
|
|
|
2) Create a new Flutter project:
|
|
|
|
```bash
|
|
flutter create sheetjs_flutter
|
|
cd sheetjs_flutter
|
|
```
|
|
|
|
3) Start the Android emulator.
|
|
|
|
<details open><summary><b>Details</b> (click to hide)</summary>
|
|
|
|
**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**
|
|
|
|
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
|
|
```
|
|
|
|
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:
|
|
|
|
```bash
|
|
emulator -avd Pixel_3a_API_34
|
|
```
|
|
|
|
:::note pass
|
|
|
|
On macOS, `~/Library/Android/sdk/emulator/` is the typical location for the
|
|
`emulator` binary. If it cannot be found, add the folder to `PATH`:
|
|
|
|
```bash
|
|
export PATH="$PATH":~/Library/Android/sdk/emulator
|
|
emulator -avd Pixel_3a_API_34
|
|
```
|
|
|
|
:::
|
|
|
|
</details>
|
|
|
|
4) While the Android emulator is open, start the application:
|
|
|
|
```bash
|
|
flutter run
|
|
```
|
|
|
|
<details><summary><b>If emulator is not detected</b> (click to show)</summary>
|
|
|
|
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:
|
|
|
|
```text title="Command output"
|
|
// highlight-next-line
|
|
[ +6 ms] No supported devices found with name or id matching 'sheetjs'.
|
|
[ ] The following devices were found:
|
|
...
|
|
// highlight-next-line
|
|
[ +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:
|
|
|
|
```bash
|
|
flutter -v -d emulator-5554 run
|
|
```
|
|
|
|
</details>
|
|
|
|
Once the app loads, stop the terminal process and close the simulator.
|
|
|
|
5) Install Flutter / Dart dependencies:
|
|
|
|
```bash
|
|
flutter pub add http csv flutter_js
|
|
```
|
|
|
|
6) Open `pubspec.yaml` with a text editor. Search for the line that starts with
|
|
`flutter:` (no whitespace) and add the highlighted lines:
|
|
|
|
```yaml title="pubspec.yaml"
|
|
# The following section is specific to Flutter packages.
|
|
flutter:
|
|
// highlight-start
|
|
assets:
|
|
- scripts/xlsx.full.min.js
|
|
- scripts/shim.min.js
|
|
// highlight-end
|
|
```
|
|
|
|
7) Download dependencies to the `scripts` folder:
|
|
|
|
<CodeBlock language="bash">{`\
|
|
mkdir -p scripts
|
|
cd scripts
|
|
curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js
|
|
curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/shim.min.js
|
|
cd ..`}
|
|
</CodeBlock>
|
|
|
|
8) Download [`main.dart`](pathname:///flutter/main.dart) to `lib/main.dart`:
|
|
|
|
```bash
|
|
curl -L -o lib/main.dart https://docs.sheetjs.com/flutter/main.dart
|
|
```
|
|
|
|
### Android
|
|
|
|
9) Start the Android emulator using the same instructions as Step 3.
|
|
|
|
10) Launch the app:
|
|
|
|
```bash
|
|
flutter run
|
|
```
|
|
|
|
The app fetches <https://sheetjs.com/pres.numbers>, parses, converts data to an
|
|
array of arrays, and presents the data in a Flutter `Table` widget.
|
|
|
|
:::caution pass
|
|
|
|
When the demo was last run, there was a build 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 │
|
|
│ } │
|
|
│ } │
|
|
```
|
|
|
|
This was fixed by editing `android/app/build.gradle`.
|
|
|
|
Searching for `minSdkVersion` should reveal the following line:
|
|
|
|
```text title="android\app\build.gradle"
|
|
minSdkVersion flutter.minSdkVersion
|
|
```
|
|
|
|
`flutter.minSdkVersion` should be replaced with `21`:
|
|
|
|
```text title="android\app\build.gradle"
|
|
minSdkVersion 21
|
|
```
|
|
|
|
:::
|
|
|
|
11) Close the Android emulator.
|
|
|
|
### iOS
|
|
|
|
12) Start the iOS simulator.
|
|
|
|
13) Launch the app:
|
|
|
|
```bash
|
|
flutter run
|
|
```
|
|
|
|
The app fetches <https://sheetjs.com/pres.numbers>, parses, converts data to an
|
|
array of arrays, and presents the data in a Flutter `Table` widget.
|
|
|
|
### Android Device
|
|
|
|
14) Connect an Android device using a USB cable.
|
|
|
|
If the device asks to allow USB debugging, tap "Allow".
|
|
|
|
15) Verify that `flutter` can find the device:
|
|
|
|
```bash
|
|
flutter devices
|
|
```
|
|
|
|
The list should include the device:
|
|
|
|
```
|
|
SheetJS (mobile) • 1234567890 • android-arm64 • Android 10 (API 29)
|
|
^^^^^^^--- the first column is the name
|
|
```
|
|
|
|
16) Build an APK:
|
|
|
|
```bash
|
|
flutter build apk --release
|
|
```
|
|
|
|
17) Install on the Android device:
|
|
|
|
```bash
|
|
flutter install
|
|
```
|
|
|
|
The script will 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.
|
|
|
|
18) Launch the installed `sheetjs_flutter` app on the device
|
|
|
|
:::caution pass
|
|
|
|
The app may take 30 seconds to load the content.
|
|
|
|
**There are known bugs in the Dart HTTP client in Android 12[^9].**
|
|
|
|
:::
|
|
|
|
### iOS Device
|
|
|
|
19) Follow the official "Deploy to physical iOS devices" instructions[^10]
|
|
|
|
20) Connect the iOS device and verify that `flutter` can find the device:
|
|
|
|
```bash
|
|
flutter devices
|
|
```
|
|
|
|
The list should include the device:
|
|
|
|
```
|
|
SheetPad (mobile) • 00000000-0000000000000000 • ios • iOS 15.1 19B74
|
|
^^^^^^^^--- the first column is the name
|
|
```
|
|
|
|
21) Run the program on the device:
|
|
|
|
```bash
|
|
flutter run -d SheetPad
|
|
```
|
|
|
|
[^1]: <https://dart.dev/> is the official site for the Dart Programming Language.
|
|
[^2]: <https://flutter.dev/> is the official site for the Flutter Framework.
|
|
[^3]: [The `flutter_js` package](https://pub.dev/packages/flutter_js) is hosted on the Dart package repository.
|
|
[^4]: See [the dedicated "Swift + JavaScriptCore" demo](/docs/demos/engines/jsc) for more details.
|
|
[^5]: See [the dedicated "C + QuickJS" demo](/docs/demos/engines/quickjs) for more details.
|
|
[^6]: See [`read` in "Reading Files"](/docs/api/parse-options)
|
|
[^7]: See [`sheet_to_csv` in "CSV and Text"](/docs/api/utilities/csv#delimiter-separated-output)
|
|
[^8]: See [the Flutter Installation Instructions](https://docs.flutter.dev/get-started/install)
|
|
[^9]: For example, [Issue 836 in the `http` repository](https://github.com/dart-lang/http/issues/836) mentions that API calls may take 10+ seconds. This is an issue in Dart + Flutter.
|
|
[^10]: See ["Deploy to physical iOS devices"](https://docs.flutter.dev/get-started/install/macos?tab=ios15#deploy-to-physical-ios-devices) in the Flutter documentation |