11 KiB
title | sidebar_label | description | pagination_prev | pagination_next | sidebar_position | sidebar_custom_props | ||
---|---|---|---|---|---|---|---|---|
Let Data Fly with Flutter | Dart + Flutter | 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. | demos/static/index | demos/desktop/index | 6 |
|
import current from '/version.js'; import CodeBlock from '@theme/CodeBlock';
Dart1 + Flutter2 is a popular cross-platform app framework. JavaScript code can be run through embedded engines.
SheetJS 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:
iOS | Android |
---|---|
:::warning Telemetry
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
:::note pass
This demo assumes familiarity with Dart and Flutter.
:::
For the iOS and Android targets, the flutter_js
package3 wraps JavaScriptCore4
and QuickJS5 engines respectively.
The SheetJS Standalone scripts 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:
flutter:
assets:
- scripts/xlsx.full.min.js
- scripts/shim.min.js
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
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> {
// 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:
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:
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
function6 can read the
Base64-encoded string and the sheet_to_csv
utility function7 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
:
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
:::note
This demo was tested on an Intel Mac on 2023-08-18 with Flutter 3.13.0, Dart
3.1.0, and flutter_js
0.8.0.
The iOS simulator runs iOS 16.2 on an iPhone 14 Pro Max.
The Android simulator runs Android 12.0 (S) API 31 on a Pixel 3.
:::
- Follow the official "Install" instructions for Flutter8.
Run flutter doctor
and confirm the following items are checked:
[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 14.2)
[✓] Android Studio (version 2022.1)
(the actual version numbers may differ)
Installation Notes (click to hide)
:::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)
:::
:::caution 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:
export CHROME_EXECUTABLE=/Applications/Chromium.app/Contents/MacOS/Chromium
:::
Run flutter emulators
and check for both ios
and android
emulators:
apple_ios_simulator • iOS Simulator • Apple • ios
Pixel_3_API_31 • Pixel 3 API 31 • Google • android
- Disable telemetry.
dart --disable-telemetry
dart --disable-analytics
flutter config --no-analytics
flutter config --disable-telemetry
Base Project
- Create a new Flutter project:
flutter create sheetjs_flutter
cd sheetjs_flutter
-
Open the iOS simulator.
-
While the iOS simulator is open, start the application:
flutter run
Once the app loads, stop the terminal process and close the simulator.
- Install Flutter / Dart dependencies:
flutter pub add http csv flutter_js
- Open
pubspec.yaml
with a text editor. Search for the line that starts withflutter:
(no whitespace) and add the highlighted lines:
# The following section is specific to Flutter packages.
flutter:
// highlight-start
assets:
- scripts/xlsx.full.min.js
- scripts/shim.min.js
// highlight-end
- Download dependencies to the
scripts
folder:
{\ 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 ..
}
- Download
main.dart
tolib/main.dart
:
curl -L -o lib/main.dart https://docs.sheetjs.com/flutter/main.dart
iOS
-
Start the iOS simulator again.
-
Launch the app:
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.
- Close the iOS simulator.
Android
- Start the Android emulator with
emulator -avd name_of_device
. The actual emulator name can be found withflutter emulators
:
% flutter emulators
2 available emulators:
apple_ios_simulator • iOS Simulator • Apple • ios
Pixel_3_API_31 • Pixel 3 API 31 • Google • android
^^^^^^^^^^^^^^--- the first column is the name
% emulator -avd Pixel_3_API_31
- Launch the app:
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:
minSdkVersion flutter.minSdkVersion
flutter.minSdkVersion
should be replaced with 21
:
- minSdkVersion flutter.minSdkVersion
+ minSdkVersion 21
:::
-
https://dart.dev/ is the official site for the Dart Programming Language. ↩︎
-
https://flutter.dev/ is the official site for the Flutter Framework. ↩︎
-
The
flutter_js
package is hosted on the Dart package repository. ↩︎ -
See the dedicated "Swift + JavaScriptCore" demo for more details. ↩︎
-
See the dedicated "C + QuickJS" demo for more details. ↩︎