之前分享瞭如何在Flutter插件中支持Android和Windows,這篇文章將增加Web插件的實現方法,以及創建一個簡單的web一維碼,二維碼識別應用。
參考資源
- https://dart.dev/web/js-interop
- https://github.com/grandnexus/firebase-dart
- https://pub.dev/packages/js
開發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_picker
和flutter_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