Flutter Web插件實現:打通JavaScript和Dart

之前分享瞭如何在Flutter插件中支持Android和Windows,這篇文章將增加Web插件的實現方法,以及創建一個簡單的web一維碼,二維碼識別應用。

參考資源

開發Flutter Web插件

Web插件開發,主要問題是如何實現Dart和JavaScript的相互調用。官網提供的firebase_web示例值得學習和參考。

初始化web插件

在當前的插件工程中增加Web模板:

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

和Windows, Android不同,web模板沒有生成一個叫web的目錄,也沒有生成任何的JavaScritp代碼文件。我們只看到一個新的flutter_barcode_sdk_web.dart文件。

接下來把插件聲明添加到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

如何實現JavaScript和Dart交互

和其它平臺一樣,handleMethodCall()是入口:

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}\'',
        );
    }
  }

但不同的是,web並不需要在插件中引入依賴庫編譯,我們要做的只是定義接口。

我定義了兩個接口: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();
}

在視頻模式下,結果是通過回調返回的。然而,在上層的flutter_barcode_sdk.dart中,回調函數引用沒有辦法通過invokeMethod傳遞下來。我的解決方法是使用全局變量保存回調函數:

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

爲了避免和其它平臺衝突,這個變量單獨定義在一個global.dart 文件中:

Function globalCallback = () => {};

現在打開barcode_manager.dart,根據dynamsoft-javascript-barcode定義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);
}

爲了實現JavaScript的Promise,我們需要在另外一個utils.dart文件中定義:

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);

接下來實現對象初始化:

/// 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)});

Dart的函數需要通過allowInterop()封裝才能夠被JavaScript調用。

實現decodeFile()

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

    return _resultWrapper(barcodeResults);
  }

實現decodeVideo()回調:

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

創建Web一維碼,二維碼識別程序

創建一個新的Flutter工程,並在web/index.html中添加<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/dbr.js" data-productKeys="PRODUCT-KEYS"></script>

pubspec.yaml中添加image_pickerflutter_barcode_sdk:

dependencies:
  flutter_barcode_sdk:
  image_picker:

在UI中添加兩個按鈕,一個用於加載圖片,一個用於開啓攝像頭視頻流:

final picker = ImagePicker();

@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)});
                      }),
                ]),
          ),
        ])),
  );

最後運行程序:

flutter run -d chrome

在這裏插入圖片描述

Flutter插件下載

https://pub.dev/packages/flutter_barcode_sdk

源碼

https://github.com/yushulx/flutter_barcode_sdk

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章