🤳

Flutter で QRコードリーダー

2022/02/03に公開3

まず自前で作るのは修羅の道過ぎるので、当然のことながらライブラリを探してみました。

要件

  • iOS, Android 両方で動く
  • 紙に映っている 3cm 四方くらいのQRコードが読み取れる
  • 分割QRコードで近くに他のQRコードがあっても読み取れる

選択肢

qr_code_scanner

これが一番ポピュラーそう。

https://pub.dev/packages/qr_code_scanner

zxing2

ZXing という有名なQRコードリーダーを内臓したライブラリです。

https://pub.dev/packages/zxing2

qrscan

Android のみっぽいので今回は対象外としました

https://pub.dev/packages/qrscan

fast_qr_reader_view

  • Display live camera preview in a widget.
  • Uses native AVFoundation code detection in iOS
  • Uses ML Kit in Android

https://github.com/facundomedica/fast_qr_reader_view

flutter_qrcode_reader

https://github.com/bcko/flutter_qrcode_reader

精度について確かめる

全部やるのは大変なので、ポピュラーそうなものから試していって、要件である「紙に映っている 3cm 四方くらいのQRコードが読み取れる」ができるかをテストしていきました。

結論から申し上げますと qr_code_scanner でバッチリでしたので、そこで対応したことを書いていきます。

実装

まずはインストール。

flutter pub add qr_code_scanner

カメラの権限が必要なので plist に以下を追加します。

<key>NSCameraUsageDescription</key>
<string>QRコードの読み取りなどに使います</string>

そして、QRコード読み取り用のコンポーネントを作ります。
(Example のコードをコピペして動作確認に最低限のところだけ残した形です)
(諸事情で non-null safety に変えています。)

import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';

class QRCodeReader extends StatefulWidget {
  const QRCodeReader({Key? key}) : super(key: key);

  
  State<StatefulWidget> createState() => _QRCodeReaderState();
}

class _QRCodeReaderState extends State<QRCodeReader> {
  Barcode result;
  QRViewController controller;
  final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');

  // In order to get hot reload to work we need to pause the camera if the platform
  // is android, or resume the camera if the platform is iOS.
  
  void reassemble() {
    super.reassemble();
    if (Platform.isAndroid) {
      controller.pauseCamera();
    }
    controller.resumeCamera();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: <Widget>[
          Expanded(flex: 4, child: _buildQrView(context)),
          Expanded(
            flex: 1,
            child: FittedBox(
              fit: BoxFit.contain,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: <Widget>[
                  if (result != null)
                    Text(
                        'Barcode Type: ${describeEnum(result!.format)}   Data: ${result.code}')
                  else
                    const Text('Scan a code'),
                ],
              ),
            ),
          )
        ],
      ),
    );
  }

  // QRコードを読み取る枠の部分
  Widget _buildQrView(BuildContext context) {
    // For this example we check how width or tall the device is and change the scanArea and overlay accordingly.
    var scanArea = (MediaQuery.of(context).size.width < 400 ||
            MediaQuery.of(context).size.height < 400)
        ? 150.0
        : 300.0;
    // To ensure the Scanner view is properly sizes after rotation
    // we need to listen for Flutter SizeChanged notification and update controller
    return QRView(
      key: qrKey,
      onQRViewCreated: _onQRViewCreated,
      overlay: QrScannerOverlayShape(
          borderColor: Colors.red,
          borderRadius: 10,
          borderLength: 30,
          borderWidth: 10,
          cutOutSize: scanArea),
      onPermissionSet: (ctrl, p) => _onPermissionSet(context, ctrl, p),
    );
  }

  void _onQRViewCreated(QRViewController controller) {
    setState(() {
      this.controller = controller;
    });
    controller.scannedDataStream.listen((scanData) {
      setState(() {
        result = scanData;
      });
    });
  }

  void _onPermissionSet(BuildContext context, QRViewController ctrl, bool p) {
    print('${DateTime.now().toIso8601String()}_onPermissionSet $p');
    if (!p) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('no Permission')),
      );
    }
  }

  
  void dispose() {
    controller?.dispose();
    super.dispose();
  }
}

そして実機で検証したら動きました。やったね!!
ちなみに要件のところに書いた以下もちゃんと印刷して確認できました(個人情報が載っているのでこちらにはスクショ貼れないですが。)

  • 紙に映っている 3cm 四方くらいのQRコードが読み取れる
  • 分割QRコードで近くに他のQRコードがあっても読み取れる

ちなみに result に入ってくるものの型は次のようになっています。
rawBytes も取得できるので分割QRもバッチリそうですね👌

import 'barcode_format.dart';

/// The [Barcode] object holds information about the barcode or qr code.
///
/// [code] is the string-content of the barcode.
/// [format] displays which type the code is.
/// Only for Android and iOS, [rawBytes] gives a list of bytes of the result.
class Barcode {
  Barcode(this.code, this.format, this.rawBytes);

  final String? code;
  final BarcodeFormat format;

  /// Raw bytes are only supported by Android and iOS.
  final List<int>? rawBytes;
}

Discussion

masato_is_a_tomatomasato_is_a_tomato

rawBytesから分割QRの位置を判別するやり方を教えてほしいです。
例えば、rawBytes.firstの値が30なら1番左、31なら左から2番目みたいな位置の判定方法を思い付きましたがこれで問題ないでしょうか?

masato_is_a_tomatomasato_is_a_tomato

なるほど!、車検証を読み取る機能を実装していましたが、だいぶ的外れな実装をしていましたw
ありがとうございます!