--- 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:
iOS Android
![iOS screenshot](pathname:///flutter/ios.png) ![Android screenshot](pathname:///flutter/and.png)
:::note Tested Deployments This demo was tested in the following environments: **Simulators** | OS | Device | Dart | Flutter | Dev Platform | Date | |:-----------|:------------------|:--------|:---------|:-------------|:-----------| | Android 34 | Pixel 3a | `3.2.2` | `3.16.2` | `darwin-x64` | 2023-12-04 | | iOS 17.0.1 | iPhone 15 Pro Max | `3.2.2` | `3.16.2` | `darwin-x64` | 2023-12-04 | **Real Devices** | OS | Device | Dart | Flutter | Date | |:-----------|:------------------|:--------|:---------|:-----------| | Android 29 | NVIDIA Shield | `3.2.2` | `3.16.2` | 2023-12-04 | | iOS 15.1 | iPad Pro | `3.2.2` | `3.16.2` | 2023-12-04 | ::: :::danger 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 { // 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 { String _version = '0.0.0'; late JavascriptRuntime _engine; @override void initState() { _engine = getJavascriptRuntime(); _initEngine(); // note: this is not `await`-ed } Future _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>` (Dart's spiritual equivalent of the array of arrays). The following snippet generates `List>` from a Dart `Uint8List`: ```dart import 'dart:convert'; import 'package:csv/csv.dart'; class SheetJSFlutterState extends State { List> _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)
Installation Notes (click to hide) :::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 ``` :::
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 (click to hide) **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 ``` :::
4) While the Android emulator is open, start the application: ```bash 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: ```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 ```
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: {`\ 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 ..`} 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://docs.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://docs.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