diff --git a/docz/docs/03-demos/05-mobile/06-flutter.md b/docz/docs/03-demos/05-mobile/06-flutter.md
new file mode 100644
index 0000000..7b7adcb
--- /dev/null
+++ b/docz/docs/03-demos/05-mobile/06-flutter.md
@@ -0,0 +1,231 @@
+---
+title: Flutter
+pagination_prev: demos/static/index
+pagination_next: demos/desktop/index
+sidebar_position: 5
+sidebar_custom_props:
+ summary: Dart + JS Interop
+---
+
+import current from '/version.js';
+import CodeBlock from '@theme/CodeBlock';
+
+Dart + Flutter is a cross-platform alternative to [JS + React Native](/docs/demos/mobile/reactnative).
+
+For the iOS and Android targets, the `flutter_js` package wraps JavaScriptCore
+and QuickJS engines respectively.
+
+The [Standalone scripts](/docs/getting-started/installation/standalone) can be
+parsed and evaluated in the wrapped engines.
+
+The "Complete Example" creates an app that looks like the screenshots below:
+
+
+ iOS |
+
+
+![iOS screenshot](pathname:///flutter/ios.png)
+
+ |
+
+:::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
+
+This demo assumes familiarity with Dart and Flutter.
+
+### 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 asychronous, it is recommended to create a wrapper
+`async` function and sequentially await each script:
+
+```dart
+class SheetJSFlutterState extends State {
+ String _version = '0.0.0';
+
+ @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 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 before parsing.
+
+The `csv` package provides a special `CsvToListConverter` converter to generate
+`List>` (Dart's spiritual equivalent of the array of arrays):
+
+```dart
+import 'dart:convert';
+import 'package:http/http.dart' as http;
+import 'package:csv/csv.dart';
+
+class SheetJSFlutterState extends State {
+ List> _data = [];
+
+ 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 May 31 with Flutter 3.10.2,
+Dart 3.0.2, and `flutter_js` 0.7.0
+
+The iOS simulator runs iOS 16.2 on an iPhone 14 Pro Max.
+
+:::
+
+### Base Project
+
+1) Disable telemetry.
+
+```bash
+dart --disable-telemetry
+dart --disable-analytics
+flutter config --no-analytics
+flutter config --disable-telemetry
+```
+
+2) Create a new Flutter project:
+
+```bash
+flutter create sheetjs_flutter
+cd sheetjs_flutter
+```
+
+3) Open the iOS simulator
+
+4) While the iOS simulator is open, start the application:
+
+```bash
+flutter run
+```
+
+Once the app loads in the simulator, stop the terminal process.
+
+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
+```
+
+9) Launch the app:
+
+```bash
+flutter run
+```
+
+The app fetches , parses, converts data to an
+array of arrays, and presents the data in a Flutter `Table` widget.
diff --git a/docz/docusaurus.config.js b/docz/docusaurus.config.js
index b3e9df2..d651c23 100644
--- a/docz/docusaurus.config.js
+++ b/docz/docusaurus.config.js
@@ -145,7 +145,7 @@ const config = {
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
- additionalLanguages: [ "visual-basic", "swift", "java", "csharp", "perl", "ruby", "cpp", "applescript", "liquid", "rust" ],
+ additionalLanguages: [ "visual-basic", "swift", "java", "csharp", "perl", "ruby", "cpp", "applescript", "liquid", "rust", "dart" ],
},
liveCodeBlock: {
playgroundPosition: 'top'
diff --git a/docz/static/flutter/ios.png b/docz/static/flutter/ios.png
new file mode 100644
index 0000000..c5ba8b7
Binary files /dev/null and b/docz/static/flutter/ios.png differ
diff --git a/docz/static/flutter/main.dart b/docz/static/flutter/main.dart
new file mode 100644
index 0000000..5a961bd
--- /dev/null
+++ b/docz/static/flutter/main.dart
@@ -0,0 +1,100 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart' show rootBundle;
+import 'dart:typed_data';
+import 'dart:convert';
+import 'package:http/http.dart' as http;
+import 'package:flutter_js/flutter_js.dart';
+import 'package:csv/csv.dart';
+import 'package:collection/collection.dart';
+
+void main() {
+ runApp(const MyApp());
+}
+
+class MyApp extends StatelessWidget {
+ const MyApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ debugShowCheckedModeBanner: false,
+ title: 'SheetJS x Flutter Demo',
+ theme: ThemeData(
+ colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
+ useMaterial3: true,
+ ),
+ home: const SheetJSFlutter(),
+ );
+ }
+}
+
+class SheetJSFlutter extends StatefulWidget {
+ const SheetJSFlutter({super.key});
+
+ @override
+ State createState() => SheetJSFlutterState();
+}
+
+class SheetJSFlutterState extends State {
+ String _version = '0.0.0';
+ List> _data = [];
+ late JavascriptRuntime _engine;
+
+ @override
+ void initState() {
+ super.initState();
+ _async();
+ }
+
+ void _async() async {
+ await _initEngine();
+ await _fetch();
+ }
+
+ Future _initEngine() async {
+ /* load scripts */
+ _engine = getJavascriptRuntime();
+ String shim = await rootBundle.loadString("scripts/shim.min.js");
+ _engine.evaluate(shim);
+ String sheetjs = await rootBundle.loadString("scripts/xlsx.full.min.js");
+ _engine.evaluate(sheetjs);
+ JsEvalResult vers = _engine.evaluate("XLSX.version");
+ setState(() => _version = vers.stringResult);
+ }
+
+ Future _fetch() async {
+ final res = await http.get(Uri.parse("https://sheetjs.com/pres.numbers"));
+ if (res.statusCode == 200)
+ _processBytes(res.bodyBytes);
+ else
+ throw Exception("Failed to fetch file");
+ }
+
+ 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]]);
+ """);
+ setState(() {
+ _data = CsvToListConverter(eol: "\n").convert(func.stringResult);
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ backgroundColor: Theme.of(context).colorScheme.inversePrimary,
+ title: Text('SheetJS x Flutter $_version'),
+ ),
+ body: Table(
+ children: _data.mapIndexed((R, row) => TableRow(
+ decoration: BoxDecoration(color: R == 0 ? Colors.blue[50] : null),
+ children: row.mapIndexed((C, cell) => Text(cell.toString())).toList()
+ )).toList()
+ ),
+ );
+ }
+}
+