flutter
This commit is contained in:
parent
686b5c55af
commit
aa59cc10ad
231
docz/docs/03-demos/05-mobile/06-flutter.md
Normal file
231
docz/docs/03-demos/05-mobile/06-flutter.md
Normal file
@ -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
BIN
docz/static/flutter/ios.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 82 KiB |
100
docz/static/flutter/main.dart
Normal file
100
docz/static/flutter/main.dart
Normal file
@ -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()
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user