docs.sheetjs.com/docz/docs/03-demos/05-mobile/06-flutter.md

455 lines
13 KiB
Markdown
Raw Normal View History

2023-06-01 04:41:12 +00:00
---
2023-08-18 20:39:12 +00:00
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.
2023-06-01 04:41:12 +00:00
pagination_prev: demos/static/index
pagination_next: demos/desktop/index
2023-08-18 20:39:12 +00:00
sidebar_position: 6
2023-06-01 04:41:12 +00:00
sidebar_custom_props:
summary: Dart + JS Interop
---
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
2023-08-18 20:39:12 +00:00
Dart[^1] + Flutter[^2] is a popular cross-platform app framework. JavaScript
code can be run through [embedded engines](/docs/demos/engines).
2023-06-01 04:41:12 +00:00
2023-08-18 20:39:12 +00:00
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
2023-06-01 04:41:12 +00:00
2023-08-18 20:39:12 +00:00
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.
2023-06-01 04:41:12 +00:00
2023-08-18 20:39:12 +00:00
The "Demo" creates an app that looks like the screenshots below:
2023-06-01 04:41:12 +00:00
<table><thead><tr>
<th><a href="#demo">iOS</a></th>
2023-08-18 20:39:12 +00:00
<th><a href="#demo">Android</a></th>
2023-06-01 04:41:12 +00:00
</tr></thead><tbody><tr><td>
![iOS screenshot](pathname:///flutter/ios.png)
2023-08-18 20:39:12 +00:00
</td><td>
![Android screenshot](pathname:///flutter/and.png)
2023-06-01 04:41:12 +00:00
</td></tr></tbody></table>
:::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
2023-08-18 20:39:12 +00:00
:::note pass
2023-06-01 04:41:12 +00:00
This demo assumes familiarity with Dart and Flutter.
2023-08-18 20:39:12 +00:00
:::
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.
2023-06-01 04:41:12 +00:00
### 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
2023-06-01 08:25:44 +00:00
Since fetching assets is asynchronous, it is recommended to create a wrapper
2023-06-01 04:41:12 +00:00
`async` function and sequentially await each script:
```dart
class SheetJSFlutterState extends State<SheetJSFlutter> {
String _version = '0.0.0';
2023-08-18 20:39:12 +00:00
late JavascriptRuntime _engine;
2023-06-01 04:41:12 +00:00
@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
2023-08-18 20:39:12 +00:00
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
```
2023-06-01 04:41:12 +00:00
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
2023-08-18 20:39:12 +00:00
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.
2023-06-01 04:41:12 +00:00
The `csv` package provides a special `CsvToListConverter` converter to generate
2023-08-18 20:39:12 +00:00
`List<List<dynamic>>` (Dart's spiritual equivalent of the array of arrays).
The following snippet generates `List<List<dynamic>>` from a Dart `Uint8List`:
2023-06-01 04:41:12 +00:00
```dart
import 'dart:convert';
import 'package:csv/csv.dart';
class SheetJSFlutterState extends State<SheetJSFlutter> {
List<List<dynamic>> _data = [];
2023-08-18 20:39:12 +00:00
late JavascriptRuntime _engine;
2023-06-01 04:41:12 +00:00
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); });
}
2023-08-18 20:39:12 +00:00
}
2023-06-01 04:41:12 +00:00
```
## Demo
:::note
2023-09-05 18:04:23 +00:00
The Android demo was last tested on 2023 September 03 with Flutter `3.13.2`. The
simulator used Android 13 ("Tiramisu") API 33 on a Pixel 3.
2023-06-01 04:41:12 +00:00
2023-09-05 18:04:23 +00:00
The iOS demo was last tested on 2023 September 03 with Flutter `3.13.2`. The
simulator used iOS 16.4 on an iPhone SE (3rd generation).
2023-06-01 04:41:12 +00:00
2023-09-05 18:04:23 +00:00
Both tests used Dart 3.1.0 and Flutter JS plugin version `0.8.0`.
2023-08-18 20:39:12 +00:00
:::
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 33.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 14.2)
[✓] Android Studio (version 2022.1)
```
(the actual version numbers may differ)
<details open><summary><b>Installation Notes</b> (click to hide)</summary>
:::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:
```bash
export CHROME_EXECUTABLE=/Applications/Chromium.app/Contents/MacOS/Chromium
```
2023-06-01 04:41:12 +00:00
:::
2023-08-18 20:39:12 +00:00
</details>
Run `flutter emulators` and check for both `ios` and `android` emulators:
```
apple_ios_simulator • iOS Simulator • Apple • ios
2023-09-05 18:04:23 +00:00
Pixel_3_API_33 • Pixel 3 API 33 • Google • android
2023-08-18 20:39:12 +00:00
```
2023-06-01 04:41:12 +00:00
1) Disable telemetry.
```bash
dart --disable-telemetry
dart --disable-analytics
flutter config --no-analytics
flutter config --disable-telemetry
```
2023-08-18 20:39:12 +00:00
### Base Project
2023-06-01 04:41:12 +00:00
2) Create a new Flutter project:
```bash
flutter create sheetjs_flutter
cd sheetjs_flutter
```
2023-09-05 18:04:23 +00:00
3) Start the Android emulator.
2023-06-01 04:41:12 +00:00
2023-09-05 18:04:23 +00:00
<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_3_API_33 • Pixel 3 API 33 • 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_3_API_33` and the launch command was:
```
% emulator -avd Pixel_3_API_33
```
</details>
4) While the Android emulator is open, start the application:
2023-06-01 04:41:12 +00:00
```bash
flutter run
```
2023-09-05 18:04:23 +00:00
<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>
2023-08-18 20:39:12 +00:00
Once the app loads, stop the terminal process and close the simulator.
2023-06-01 04:41:12 +00:00
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
```
2023-08-18 20:39:12 +00:00
### Android
2023-09-05 18:04:23 +00:00
9) Start the Android emulator using the same instructions as Step 3.
2023-08-18 20:39:12 +00:00
2023-09-05 18:04:23 +00:00
10) Launch the app:
2023-06-01 04:41:12 +00:00
```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.
2023-08-18 20:39:12 +00:00
:::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`:
2023-09-05 18:04:23 +00:00
```text title="android\app\build.gradle"
minSdkVersion 21
2023-08-18 20:39:12 +00:00
```
:::
2023-09-05 18:04:23 +00:00
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.
2023-08-18 20:39:12 +00:00
[^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)