This commit is contained in:
SheetJS 2023-06-01 00:41:12 -04:00
parent 686b5c55af
commit aa59cc10ad
4 changed files with 332 additions and 1 deletions

@ -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:
<table><thead><tr>
<th><a href="#demo">iOS</a></th>
</tr></thead><tbody><tr><td>
![iOS screenshot](pathname:///flutter/ios.png)
</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
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<SheetJSFlutter> {
// 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<SheetJSFlutter> {
String _version = '0.0.0';
@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 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<List<dynamic>>` (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<SheetJSFlutter> {
List<List<dynamic>> _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:
<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
```
9) 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.

@ -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'

BIN
docz/static/flutter/ios.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

@ -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<SheetJSFlutter> createState() => SheetJSFlutterState();
}
class SheetJSFlutterState extends State<SheetJSFlutter> {
String _version = '0.0.0';
List<List<dynamic>> _data = [];
late JavascriptRuntime _engine;
@override
void initState() {
super.initState();
_async();
}
void _async() async {
await _initEngine();
await _fetch();
}
Future<void> _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<void> _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()
),
);
}
}