How to Build Web Document Scanning Applications with Flutter
Tags: flutter, webdev, dart, javascript
Published: 2022-11-16 08:27:04

This article aims to demonstrate how to build a Flutter web plugin with [Dynamic Web TWAIN](https://www.dynamsoft.com/web-twain/overview/), as well as how to use the plugin to build web document scanning applications.

## Flutter Web TWAIN Package
[https://pub.dev/packages/flutter_web_twain](https://pub.dev/packages/flutter_web_twain)

## Flutter Wrapper for Dynamic Web TWAIN
We use the `--template=plugin` and `--platforms=web` flags to create a new Flutter plugin project for web development.

```bash
flutter create --org com.dynamsoft --template=plugin --platforms=web flutter_web_twain
```

To interop JavaScript with Dart, add the [js package](https://pub.dev/documentation/js/latest/) as the dependency in `pubspec.yaml` file:

```yaml
dependencies:
  ...
  js: ^0.6.3
```

Create a `lib/src/web_twain_manager.dart` file, in which we define the JavaScript namespace and classes that we want to use in Dart. 

```dart
@JS('Dynamsoft')
library dynamsoft;

import 'package:js/js.dart';
import 'dart:html' as html;
import 'utils.dart';

@JS('DWT')
class DWT {
  external static set ResourcesPath(String resourcePath);
  external static set ProductKey(String productKey);
  external static void Load();
  external static void Unload();
  external static void CreateDWTObjectEx(
      dynamic obj, Function success, Function error);
  external static set AutoLoad(bool autoLoad);
}

@JS('DWTObject')
class DWTObject {
  external set IfShowUI(bool ifShowUI);
  external dynamic get Viewer;
  external void OpenSource();
  external void CloseSource();
  external void AcquireImage(dynamic obj, Function success, Function error);
  external void LoadImageEx(
      String path, int type, Function success, Function error);
  external int get HowManyImagesInBuffer;
  external set IfShowFileDialog(bool ifShowFileDialog);
  external int get CurrentImageIndexInBuffer;
  external void SaveAllAsPDF(String path, Function success, Function error);
  external void SaveAllAsMultiPageTIFF(
      String path, Function success, Function error);
  external void SaveAsJPEG(String path, int index);
  external int GetImageBitDepth(int index);
  external void ConvertToGrayScale(int index);
}

@JS('Viewer')
class Viewer {
  external void bind(dynamic container);
  external void show();
}
```

The methods declared above can be used for document scan, load and save, which are a small parts of the whole API. For more advanced functionalities, please refer to [Dynamic Web TWAIN API Reference](https://www.dynamsoft.com/web-twain/docs/info/api/). Note: The Flutter release mode obfuscates the JavaScript code, so it is necessary to use the `@JS` annotation to expose all JavaScript properties and methods that will be called in your Flutter project.

Next, we create a `WebTwainManager` class and implement the following methods:
- `init()`: Set the Dynamic Web TWAIN resource path and the [license key](https://www.dynamsoft.com/customer/license/trialLicense/?product=dwt).

    ```dart
    class WebTwainManager {
      DWTObject? _webTwain;
      String _containerId = '';
      html.Element? _container;

      init(String path, String key) {
        DWT.ResourcesPath = path;
        DWT.ProductKey = key;
        DWT.AutoLoad = false;
      }
    }
    ```
- `dispose()`: Destroy all web TWAIN instances.

    ```dart
    dispose() {
      DWT.Unload();
    }
    ```
- `createWebTwain()`: Create a Dynamic Web TWAIN object with an HTML div element.

    ```dart
    createContainer(html.DivElement container) {
      _container = container;
      _containerId = container.id;
      var tmp = Map<dynamic, dynamic>();
      String jsonStr = '{"WebTwainId":"container"}';
      DWT.Unload();
      DWT.CreateDWTObjectEx(parse(jsonStr), allowInterop((DWTObject obj) {
        _webTwain = obj;
        _webTwain!.IfShowUI = false;
        Viewer viewer = _webTwain!.Viewer;
        if (_container != null) {
          viewer.bind(_container);
          viewer.show();
        }
      }), allowInterop((error) {
        print('CreateDWTObjectEx error: $error');
      }));
    }
    ```
- `scan()`: Acquire documents from document scanners.

    ```dart
    scan(String config) {
      if (_webTwain != null) {
        _webTwain!.OpenSource();
        _webTwain!.AcquireImage(parse(config), allowInterop(() {
          _webTwain!.CloseSource();
        }), allowInterop((settings, errCode, errString) {
          _webTwain!.CloseSource();
          print(errString);
        }));
      }
    }
    ```

- `load()`: Load images from local files.

    ```dart
    load() {
      if (_webTwain != null) {
        _webTwain!.LoadImageEx("", 5, allowInterop(() {
          print("Successful!");
        }), allowInterop((errorCode, errorString) {
          alert(errorString);
        }));
      }
    }
    ```
- `download()`: Save document images as PDF, TIFF or JPEG files.

    ```dart
    download(int type, String filename) {
      if (_webTwain != null && _webTwain!.HowManyImagesInBuffer > 0) {
        _webTwain!.IfShowFileDialog = true;
        switch (type) {
          case 0:
            _webTwain!.SaveAllAsPDF(
                filename, allowInterop(() => {}), allowInterop(() => {}));
            break;
          case 1:
            _webTwain!.SaveAllAsMultiPageTIFF(
                filename, allowInterop(() => {}), allowInterop(() => {}));
            break;
          case 2:
            if (_webTwain!
                    .GetImageBitDepth(_webTwain!.CurrentImageIndexInBuffer) ==
                1) {
              _webTwain!.ConvertToGrayScale(_webTwain!.CurrentImageIndexInBuffer);
            }
            _webTwain!.SaveAsJPEG(filename, _webTwain!.CurrentImageIndexInBuffer);
        }
      }
    }
    ```

Dynamic Web TWAIN object binds to an HTML div element, and the document images are displayed in the div element. To make the HTML element visible in Flutter, we call `ui.platformViewRegistry.registerViewFactory` to register it as a platform view. Create a `WebTwainController` class to manage the `WebTwainManager` instance and the HTML div element.

```dart
import 'dart:async';
import 'dart:html' as html;
import 'shims/dart_ui.dart' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';

import 'web_twain_manager.dart';

class WebTwainController {
  final WebTwainManager _webTwainManager = WebTwainManager();
  final String _webviewId = '${DateTime.now().millisecondsSinceEpoch}';
  String get webviewId => _webviewId;
  final html.DivElement _twainContainer = html.DivElement();

  Future<void> init(String path, String key) async {
    ui.platformViewRegistry.registerViewFactory(
      _webviewId,
      (int id) => _twainContainer
        ..style.width = '100%'
        ..style.height = '100%',
    );

    _webTwainManager.init(path, key);
    _twainContainer.id = 'dwtcontrolContainer';
    _webTwainManager.createContainer(_twainContainer);
  }

  Future<void> dispose() async {
    _webTwainManager.dispose();
  }

  Future<void> scan(String config) async {
    _webTwainManager.scan(config);
  }

  Future<void> load() async {
    _webTwainManager.load();
  }

  Future<void> download(int type, String filename) async {
    _webTwainManager.download(type, filename);
  }
}
```

Finally, we create a `createWebTwainController()` method to instantiate the `WebTwainController` and return it to the Flutter app. There are three files need to be modified:

- `flutter_web_twain_platform_interface.dart`

    ```dart
    Future<WebTwainController?> createWebTwainController() {
      throw UnimplementedError(
          'createWebTwainController() has not been implemented.');
    }
    ```
- `flutter_web_twain.dart`
    ```dart
    Future<WebTwainController?> createWebTwainController() {
      return FlutterWebTwainPlatform.instance.createWebTwainController();
    }
    ```
- `flutter_web_twain_web.dart`
    ```dart
    @override
    Future<WebTwainController> createWebTwainController() {
      final controller = WebTwainController();
      return Future.value(controller);
    }
    ```

## Steps to Build Web Document Scanning Applications with Flutter
1. Create a new Flutter project and add the `flutter_web_twain` package.
    ```bash
    flutter create app
    cd app
    flutter pub add flutter_web_twain
    ```
2. Download Dynamic Web TWAIN via the npm command.
    ```bash
    npm install dwt
    ```
3. Include the JavaScript library in the `index.html` file.
    ```html
    <script src="node_modules/dwt/dist/dynamsoft.webtwain.min.js"></script>
    ```
    
4. Create a `ScannerWidget`:

    Initialize the `WebTwainController` object with the resource path and a valid license key.

    ```dart
    import 'package:flutter/services.dart';
    import 'package:flutter_web_twain/flutter_web_twain.dart';
    import 'package:flutter_web_twain/web_twain_controller.dart';
    import 'package:flutter/material.dart';

    class ScannerWidget extends StatefulWidget {
      const ScannerWidget({super.key});

      @override
      State<ScannerWidget> createState() => _ScannerWidgetState();
    }

    class _ScannerWidgetState extends State<ScannerWidget> {
      final FlutterWebTwain _flutterWebTwain = FlutterWebTwain();
      WebTwainController? _controller;
      PixelFormat? _pixelFormat = PixelFormat.TWPT_BW;
      FileFormat? _fileFormat = FileFormat.PDF;
      @override
      void initState() {
        super.initState();
        _setup();
      }

      @override
      void dispose() {
        _controller!.dispose();
        super.dispose();
      }

      Future<void> _setup() async {
        _controller = await _flutterWebTwain.createWebTwainController();
        await _controller!.init('node_modules/dwt/dist/', 'LICENSE-KEY');
        setState(() {});
      }
    }
    ```

    Construct the UI, including a `HtmlElementView` widget, six `Radio` widgets, and three `MaterialButton` widgets.

    ```dart
    Widget build(BuildContext context) {
      return MaterialApp(
        home: Scaffold(
            appBar: AppBar(
              title: const Text('Dynamic Web TWAIN for Flutter'),
            ),
            body: Column(children: [
              Expanded(
                child: _controller == null
                    ? const CircularProgressIndicator()
                    : HtmlElementView(viewType: _controller!.webviewId),
              ),
              SizedBox(
                child: Row(children: <Widget>[
                  Radio(
                    value: PixelFormat.TWPT_BW,
                    groupValue: _pixelFormat,
                    onChanged: (PixelFormat? value) {
                      setState(() {
                        _pixelFormat = value;
                      });
                    },
                  ),
                  const Text('BW'),
                  Radio(
                    value: PixelFormat.TWPT_GRAY,
                    groupValue: _pixelFormat,
                    onChanged: (PixelFormat? value) {
                      setState(() {
                        _pixelFormat = value;
                      });
                    },
                  ),
                  const Text('Gray'),
                  Radio(
                    value: PixelFormat.TWPT_RGB,
                    groupValue: _pixelFormat,
                    onChanged: (PixelFormat? value) {
                      setState(() {
                        _pixelFormat = value;
                      });
                    },
                  ),
                  const Text('Color'),
                  const Padding(padding: EdgeInsets.all(10)),
                  MaterialButton(
                      textColor: Colors.white,
                      color: Colors.blue,
                      onPressed: () async {
                        if (_controller != null) {
                          await _controller!.scan(
                              '{"IfShowUI": false, "PixelType": ${_pixelFormat!.index}}');
                        }
                      },
                      child: const Text('Scan Documents')),
                  const Padding(padding: EdgeInsets.all(10)),
                  MaterialButton(
                      textColor: Colors.white,
                      color: Colors.blue,
                      onPressed: () async {
                        if (_controller != null) {
                          await _controller!.load();
                        }
                      },
                      child: const Text('Load Documents'))
                ]),
              ),
              SizedBox(
                height: 100,
                child: Row(children: <Widget>[
                  Radio(
                    value: FileFormat.PDF,
                    groupValue: _fileFormat,
                    onChanged: (FileFormat? value) {
                      setState(() {
                        _fileFormat = value;
                      });
                    },
                  ),
                  const Text('PDF'),
                  Radio(
                    value: FileFormat.TIFF,
                    groupValue: _fileFormat,
                    onChanged: (FileFormat? value) {
                      setState(() {
                        _fileFormat = value;
                      });
                    },
                  ),
                  const Text('TIFF'),
                  Radio(
                    value: FileFormat.JPEG,
                    groupValue: _fileFormat,
                    onChanged: (FileFormat? value) {
                      setState(() {
                        _fileFormat = value;
                      });
                    },
                  ),
                  const Text('JPEG'),
                  const Padding(padding: EdgeInsets.all(10)),
                  MaterialButton(
                      textColor: Colors.white,
                      color: Colors.blue,
                      onPressed: () async {
                        if (_controller != null) {
                          await _controller!
                              .download(_fileFormat!.index, 'filename');
                        }
                      },
                      child: const Text('Download Documents')),
                ]),
              ),
            ])),
      );
    }
    ```
5. Edit the `main.dart` file:
    ```dart
    void main() {
      runApp(const ScannerWidget());
    }
    ```

6. Build and launch the web document scanner application:

    ```bash
    # debug
    flutter run -d chrome
    # release
    flutter run -d chrome --release
    ```

    ![Angular web document scanner](https://www.dynamsoft.com/codepool/img/2022/11/flutter-web-document-scanner.png)



## Source Code
[https://github.com/yushulx/flutter_web_twain](https://github.com/yushulx/flutter_web_twain)