Flutter で QR コードスキャンを実装できる mobile_scanner を Web アプリで試してみた

2023.09.14

こんにちは、CX事業本部 Delivery部の若槻です。

Flutter アプリケーションで QR コードスキャナーを実装したい場合には、下記のようにパッケージの選択肢がいくつかあります。

パッケージ LIKES PUB POINTS POPULARITY Last Published
mobile_scanner 1139 130 99% 38 days ago
qr_code_scanner 1798 120 99% 13 months ago
zxing2 42 140 95% 7 months agos

最も枯れているのは qr_code_scanner ですが、メンテナンスモードとなりしばらく更新されていません。よって現在最も有力なのは下記の mobile_scanner のようです。mobile_scanner は iOS、Android に加えて Web および macOS にも対応しています。

今回は mobile_scanner を使った Flutter アプリでの QR コードスキャンの実装を Web で試してみました。

試してみた

環境

$ flutter --version
Flutter 3.7.7 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 2ad6cd72c0 (6 months ago) • 2023-03-08 09:41:59 -0800
Engine • revision 1837b5be5f
Tools • Dart 2.19.4 • DevTools 2.20.1

プロジェクト作成

flutter create my_app
code my_app

パッケージ導入

flutter pub add mobile_scanner

導入されました。

$ git diff pubspec.yaml
diff --git a/pubspec.yaml b/pubspec.yaml
index 4dfe6bc..ac5348d 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -35,6 +35,7 @@ dependencies:
   # The following adds the Cupertino Icons font to your application.
   # Use with the CupertinoIcons class for iOS style icons.
   cupertino_icons: ^1.0.2
+  mobile_scanner: ^3.4.1
 
 dev_dependencies:
   flutter_test:

index.html の修正

index.htmlに下記を追加して、ZXing on Web を読み込むようにします。

<script src="https://unpkg.com/@zxing/library@0.19.1" type="application/javascript"></script>

実装

Usage を参考に実装してみます。

lib/main.dart

import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';

void main() => runApp(const MaterialApp(home: MyHome()));

class MyHome extends StatelessWidget {
  const MyHome({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Mobile Scanner')),
      body: MobileScanner(
        // fit: BoxFit.contain,
        onDetect: (capture) {
          final List<Barcode> barcodes = capture.barcodes;
          // final Uint8List? image = capture.image;
          for (final barcode in barcodes) {
            debugPrint('Barcode found! ${barcode.rawValue}');
          }
        },
      ),
    );
  }
}
  • MobileScanner()を使用すると端末のカメラが起動します。
  • onDetectを使用することにより、QR コードが読み取れた際の処理を記述できます。
  • capture.barcodesで読み取ったバーコードのリストを取得できます。
  • capture.imageで読み取った画像バイナリを取得できます。

動作確認

アプリケーションを起動します。

flutter run -d chrome

起動時にカメラのパーミッションを求められるので許可します。

AppBar 以外の部分がカメラ映像となります。QR コードをカメラにかざすと自動で読み取られます。

次のようにカメラ映像内に QR コードが映っている間は継続して読み取られます。実際の実装では、読み取りが完了したら別の画面に遷移するなどの処理を行うことになると思います。

$ flutter run -d chrome
Launching lib/main.dart on Chrome in debug mode...
Waiting for connection from debug service on Chrome...             10.3s
This app is linked to the debug service: ws://127.0.0.1:61158/fuksFUIMVMQ=/ws
Debug service listening on ws://127.0.0.1:61158/fuksFUIMVMQ=/ws

💪 Running with sound null safety 💪

🔥  To hot restart changes while running, press "r" or "R".
For a more detailed help message, press "h". To quit, press "q".

An Observatory debugger and profiler on Chrome is available at: http://127.0.0.1:61158/fuksFUIMVMQ=
The Flutter DevTools debugger and profiler on Chrome is available at: http://127.0.0.1:9103?uri=http://127.0.0.1:61158/fuksFUIMVMQ=
Barcode found! https://ja.wikipedia.org/
Barcode found! https://ja.wikipedia.org/
Barcode found! https://ja.wikipedia.org/
Barcode found! https://ja.wikipedia.org/
Barcode found! https://ja.wikipedia.org/
Barcode found! https://ja.wikipedia.org/

遭遇したエラー

前述の実装のアプリケーション実行時に下記のエラーが発生しました。

══╡ EXCEPTION CAUGHT BY SERVICES LIBRARY ╞══════════════════════════════════════════════════════════
The following MissingPluginException was thrown while activating platform stream on channel
dev.steenbakker.mobile_scanner/scanner/event:
MissingPluginException(No implementation found for method listen on channel
dev.steenbakker.mobile_scanner/scanner/event)

When the exception was thrown, this was the stack:
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 266:49  throw_
packages/flutter/src/services/platform_channel.dart 313:7                     _invokeMethod
dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 45:50            <fn>
dart-sdk/lib/async/zone.dart 1660:54                                          runUnary
dart-sdk/lib/async/future_impl.dart 147:18                                    handleValue
dart-sdk/lib/async/future_impl.dart 767:44                                    handleValueCallback
dart-sdk/lib/async/future_impl.dart 796:13                                    _propagateToListeners
dart-sdk/lib/async/future_impl.dart 567:5                                     [_completeWithValue]
dart-sdk/lib/async/future_impl.dart 640:7                                     callback
dart-sdk/lib/async/schedule_microtask.dart 40:11                              _microtaskLoop
dart-sdk/lib/async/schedule_microtask.dart 49:5                               _startMicrotaskLoop
dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 166:15           <fn>
════════════════════════════════════════════════════════════════════════════════════════════════════
Error: Expected a value of type 'MobileScannerException', but got one of type 'MissingPluginException'
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 266:49      throw_
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 99:3        castError
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 485:10  cast
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/classes.dart 638:14     as_C
packages/mobile_scanner/src/mobile_scanner.dart 142:35                            <fn>
packages/flutter/src/widgets/framework.dart 1133:30                               setState
packages/mobile_scanner/src/mobile_scanner.dart 141:9                             <fn>
dart-sdk/lib/async/zone.dart 1660:54                                              runUnary
dart-sdk/lib/async/future_impl.dart 165:22                                        handleError
dart-sdk/lib/async/future_impl.dart 779:46                                        handleError
dart-sdk/lib/async/future_impl.dart 800:13                                        _propagateToListeners
dart-sdk/lib/async/future_impl.dart 567:5                                         [_completeWithValue]
dart-sdk/lib/async/future_impl.dart 640:7                                         callback
dart-sdk/lib/async/schedule_microtask.dart 40:11                                  _microtaskLoop
dart-sdk/lib/async/schedule_microtask.dart 49:5                                   _startMicrotaskLoop
dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 166:15               <fn>

原因としては、Flutter 本体を読み込む前に ZXing on Web を読み込む必要があったためでした。追記位置に注意してください。

+  <script src="https://unpkg.com/@zxing/library@0.19.1" type="application/javascript"></script>
  <script src="flutter.js" defer></script>
-  <script src="https://unpkg.com/@zxing/library@0.19.1" type="application/javascript"></script>

おわりに

Flutter で QR コードスキャンを実装できる mobile_scanner を Web アプリで試してみました。

mobile_scanner はスコアが高いとは言いつつネット上にあまり情報がないのが心配だったのですが、使ってみると思った以上に簡単に QR コードスキャンを実装することができました。

次回は iOS アプリでも試してみたいと思います。

参考

以上