На этой неделе я предпринял новую попытку, интегрировав Dynamsoft JavaScript Barcode SDK в текущий проект Плагин Flutter barcode. Удивительно иметь возможность создавать веб-приложения для чтения штрих-кодов с помощью того же decodeFile() метода, который используется для разработки мобильных и настольных компьютеров.

То, что вы должны знать

использованная литература

Плагин Flutter Barcode SDK для веб-разработки

Вышеупомянутый firebase_web является отличным примером того, как научиться соединять API-интерфейсы Dart и JavaScript. Это очень вдохновило меня на реализацию веб-плагина Flutter.

Инициализировать плагин Flutter для Интернета

Давайте добавим шаблон веб-платформы в существующий проект плагина:

flutter create --template=plugin --platforms=web .

Команда создает только файл flutter_barcode_sdk_web.dart в папке lib. В отличие от шаблонов других платформ, которые генерируют независимые папки, содержащие некоторые языковые файлы для конкретной платформы, команда веб-плагина не создает для нас никаких файлов JavaScript.

Кроме того, нам нужно добавить описание веб-плагина в файл pubspec.yaml:

flutter:
  plugin:
    platforms:
      android:
        package: com.dynamsoft.flutter_barcode_sdk
        pluginClass: FlutterBarcodeSdkPlugin
      windows:
        pluginClass: FlutterBarcodeSdkPlugin
      web:
        pluginClass: FlutterBarcodeSdkWeb
        fileName: flutter_barcode_sdk_web.dart

Внедрение Dart API для взаимодействия с JavaScript

Подобно платформам Android и Windows, мы находим handleMethodCall() в качестве отправной точки в flutter_barcode_sdk_web.dart:

Future<dynamic> handleMethodCall(MethodCall call) async {
    switch (call.method) {
      case 'getPlatformVersion':
        return getPlatformVersion();
      default:
        throw PlatformException(
          code: 'Unimplemented',
          details:
              'flutter_barcode_sdk for web doesn\'t implement \'${call.method}\'',
        );
    }
  }

Поскольку Dynamsoft JavaScript Barcode SDK предоставляет класс Barcode Reader для статических изображений и класс Barcode Scanner для видеопотока, я создаю метод decodeFile() и метод decodeVideo() соответственно:

BarcodeManager _barcodeManager = BarcodeManager();

/// Decode barcodes from an image file.
Future<List<Map<dynamic, dynamic>>> decodeFile(String file) async {
  return _barcodeManager.decodeFile(file);
}

/// Decode barcodes from real-time video stream.
Future<void> decodeVideo() async {
  _barcodeManager.decodeVideo();
}

Метод decodeVideo() является новым и доступен только для Интернета. Он запускает встроенное средство просмотра камеры HTML5 SDK штрих-кода JavaScript. Мы можем зарегистрировать функцию обратного вызова для получения результатов декодирования штрих-кода. Метод вызова определен в flutter_barcode_sdk.dart:

Future<void> decodeVideo(Function callback) async {
  globalCallback = callback;
  await _channel.invokeMethod('decodeVideo');
}

Здесь немного сложно. Поскольку invokeMethod не может передать ссылку на функцию, мой обходной путь — назначить функцию обратного вызова глобальной переменной. Чтобы избежать конфликта сборки между разными платформами, глобальная переменная определена в одном файле global.dart:

Function globalCallback = () => {};

Теперь мы сосредоточимся на самом важном файле barcode_manager.dart, который определяет классы и функции Dart для вызова API-интерфейсов JavaScript.

@JS('Dynamsoft')
library dynamsoft;

import 'dart:convert';
import 'dart:js';
import 'package:flutter_barcode_sdk/barcode_result.dart';
import 'package:flutter_barcode_sdk/global.dart';
import 'package:js/js.dart';
import 'utils.dart';

/// BarcodeScanner class
@JS('DBR.BarcodeScanner')
class BarcodeScanner {
  external static PromiseJsImpl<BarcodeScanner> createInstance();
  external void show();
  external set onFrameRead(Function func);
}

/// BarcodeReader class
@JS('DBR.BarcodeReader')
class BarcodeReader {
  external static PromiseJsImpl<BarcodeReader> createInstance();
  external PromiseJsImpl<List<dynamic>> decode(dynamic file);
}

Следующий код взят из firebase_web. Добавьте их в файл utils.dart для обработки JavaScript Promise.

import 'dart:async';
import 'dart:js_util';
import 'package:js/js.dart';

typedef Func1<A, R> = R Function(A a);

@JS('JSON.stringify')
external String stringify(Object obj);

@JS('console.log')
external void log(Object obj);

@JS('Promise')
class PromiseJsImpl<T> extends ThenableJsImpl<T> {
  external PromiseJsImpl(Function resolver);
  external static PromiseJsImpl<List> all(List<PromiseJsImpl> values);
  external static PromiseJsImpl reject(error);
  external static PromiseJsImpl resolve(value);
}

@anonymous
@JS()
abstract class ThenableJsImpl<T> {
  external ThenableJsImpl then([Func1 onResolve, Func1 onReject]);
}

Future<T> handleThenable<T>(ThenableJsImpl<T> thenable) =>
    promiseToFuture(thenable);

Инициализация объектов Barcode Reader и Barcode Scanner осуществляется следующим образом:

/// Initialize Barcode Scanner.
void initBarcodeScanner(BarcodeScanner scanner) {
  _barcodeScanner = scanner;
  _barcodeScanner.onFrameRead = allowInterop((results) =>
      {globalCallback(callbackResults(_resultWrapper(results)))});
}

/// Initialize Barcode Reader.
void initBarcodeReader(BarcodeReader reader) {
  _barcodeReader = reader;
}

BarcodeManager() {
  handleThenable(BarcodeScanner.createInstance())
      .then((scanner) => {initBarcodeScanner(scanner)});

  handleThenable(BarcodeReader.createInstance())
      .then((reader) => {initBarcodeReader(reader)});
}

Реализация метода decodeFile() довольно проста:

Future<List<Map<dynamic, dynamic>>> decodeFile(String filename) async {
    List<dynamic> barcodeResults =
        await handleThenable(_barcodeReader.decode(filename));

    return _resultWrapper(barcodeResults);
  }

Что касается метода decodeVideo(), чтобы сделать функцию Dart вызываемой из JavaScript, мы используем allowInterop() для переноса функции обратного вызова Dart:

_barcodeScanner.onFrameRead = allowInterop((results) =>
        {globalCallback(callbackResults(_resultWrapper(results)))});

Создайте веб-считыватель штрих-кода и сканер штрих-кода

На данный момент плагин веб-штрихкода Flutter готов. Мы можем проверить это, написав простое приложение Flutter с image_picker.

Создайте новый веб-проект Flutter и добавьте <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/dbr.js" data-productKeys="PRODUCT-KEYS"></script> к web/index.html:

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/dbr.js" data-productKeys="PRODUCT-KEYS"></script>
<script src="main.dart.js" type="application/javascript"></script>

Создайте две кнопки материала. Один для декодирования штрих-кодов из статических изображений, а другой для сканирования штрих-кодов из видеопотока в реальном времени.

@override
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
        appBar: AppBar(
          title: const Text('Dynamsoft Barcode Reader'),
        ),
        body: Column(children: [
          Container(
            height: 100,
            child: Row(children: <Widget>[
              Text(
                _platformVersion,
                style: TextStyle(fontSize: 14, color: Colors.black),
              )
            ]),
          ),
          Expanded(
            child: SingleChildScrollView(
              child: Column(
                children: [
                  _file == null
                      ? Image.asset('images/default.png')
                      : Image.network(_file),
                  Text(
                    _barcodeResults,
                    style: TextStyle(fontSize: 14, color: Colors.black),
                  ),
                ],
              ),
            ),
          ),
          Container(
            height: 100,
            child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: <Widget>[
                  MaterialButton(
                      child: Text('Barcode Reader'),
                      textColor: Colors.white,
                      color: Colors.blue,
                      onPressed: () async {
                        final pickedFile =
                            await picker.getImage(source: ImageSource.camera);

                        setState(() {
                          if (pickedFile != null) {
                            _file = pickedFile.path;
                          } else {
                            print('No image selected.');
                          }

                          _barcodeResults = '';
                        });

                        if (_file != null) {
                          List<BarcodeResult> results =
                              await _barcodeReader.decodeFile(_file);
                          updateResults(results);
                        }
                      }),
                  MaterialButton(
                      child: Text('Barcode Scanner'),
                      textColor: Colors.white,
                      color: Colors.blue,
                      onPressed: () async {
                        _barcodeReader.decodeVideo(
                            (results) => {updateResults(results)});
                      }),
                ]),
          ),
        ])),
  );

Наконец, мы запускаем веб-приложение в Chrome:

flutter run -d chrome

Исходный код

https://github.com/yushulx/flutter_barcode_sdk

Первоначально опубликовано на https://www.dynamsoft.com 11 мая 2021 г.