【Flutter】webでQRコードリーダをつかう
はじめに
flutter_webでai_barcodeとflutter_riverpodを使って、自分で作成したQRコードを読み取れるアプリを作成します。
android,iosでQRコードを読み取る記事はたくさんあるのですが、flutter_webでの記事は少なかったのでまとめてみました(しかしバグ多め?)
レベル感:riverpodが少しわかるflutter初心者
最終的なlibディレクトリ内の構造
lib
├── app_barcode_scanner_widget.dart
├── error.dart
├── generated_plugin_registrant.dart//自動生成
├── main.dart
├── qr_content.dart
├── qr_detail.dart
└── qr_scan.dart
やること
pubspec.yamlの編集
ai_barcode: ^3.0.1
flutter_riverpod: ^1.0.3
上記を追加してください
flutter pub get
を忘れずに
web/jsQR.jsの追加
jsQR.js
という名でwebディレクトリに追加してください。
web/index.htmlの編集
<script src="jsQR.js" type="application/javascript"></script>
bodyタグの間に上記のスクリプトタグを追加してください。
libディレクトリ内の編集
import 'package:flutter_web_qr_code/error.dart
などのflutter_web_qr_code
の部分は、自分のプロジェクトの名前に置き換えてください。
import 'package:flutter/material.dart';
class ErrorPage extends StatelessWidget {
ErrorPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
final Object? message = ModalRoute.of(context)?.settings.arguments;
final Size size = MediaQuery.of(context).size;
double shortestSide = size.shortestSide;
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: Container(
child: message != null
? Text(message as String,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: shortestSide / 19,
))
: Text(
'エラー',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: shortestSide / 19,
),
)),
),
ElevatedButton(
onPressed: () async {
await Navigator.of(context).pushNamed('/top');
},
child: const Text('トップに戻る'))
],
));
}
}
無効のQRコードを読み取ったときに遷移するエラーページです。
import 'package:ai_barcode/ai_barcode.dart';
import 'package:flutter/material.dart';
//ai_barcodeのexampleを参考に最小化したコードスキャンカメラウィジェット
class BarcodeScannerWidget extends StatefulWidget {
BarcodeScannerWidget(this.resultCallback);
Function(String result) resultCallback;
_BarcodeScannerWidgetState createState() => _BarcodeScannerWidgetState();
}
class _BarcodeScannerWidgetState extends State<BarcodeScannerWidget> {
late ScannerController _scannerController;
void initState() {
super.initState();
_scannerController = ScannerController(scannerResult: (result) {
widget.resultCallback(result);
}, scannerViewCreated: () {
_scannerController.startCamera();
_scannerController.startCameraPreview();
});
}
void dispose() {
super.dispose();
_scannerController.stopCameraPreview();
_scannerController.stopCamera();
}
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Expanded(
child: scanCamera(),
)
],
);
}
Widget scanCamera() {
return PlatformAiBarcodeScannerWidget(
platformScannerController: _scannerController,
);
}
}
ai_barcodeのexampleを参考に最小化したコードスキャンカメラウィジェットです。
class QRContent {
QRContent(
this.id,
this.name,
);
String name;
int id;
}
List<QRContent> qrContentList = [
QRContent(0, 'いぬ'),
QRContent(1, 'ねこ'),
QRContent(2, 'さる'),
QRContent(3, 'とり'),
QRContent(4, 'きじ')
];
QRコードを読み取ったときに表示させる内容。idを読み取って、そのindexをもつqrContentListのnameやidを表示させます。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_web_qr_code/app_barcode_scanner_widget.dart';
import 'package:flutter_web_qr_code/qr_content.dart';
const String password = 'password123';
final qrContentsProvider = StateProvider<QRContent>((ref) {
return qrContentList[0];
});
final isScanedProvider = StateProvider<bool>((ref) {
return false;
});
class QRScanPage extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final bool isScaned = ref.watch(isScanedProvider.state).state;
final QRContent qrContents = ref.watch(qrContentsProvider.state).state;
final Size size = MediaQuery.of(context).size;
double shortestSide = size.shortestSide;
return Scaffold(
appBar: AppBar(title: const Text('QRコード読み取り')),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
ref.read(isScanedProvider.state).state =
false; //flagをfalseにして、読み取ったときの処理がストップしないようにする
},
child: const Text('読み取り開始')),
!isScaned
? Padding(
padding: EdgeInsets.all(shortestSide * 0.05),
child: SizedBox(
width: shortestSide * 0.6,
height: shortestSide * 0.6,
child: BarcodeScannerWidget(
(String code) async {
if (isScaned) {
//ストップを掛けなければ永遠にカメラが止まらず、スキャンしてしまう。
//1度スキャンしたらflagをtrueにしてflagがtrueの場合は処理をストップする
return;
}
ref.read(isScanedProvider.state).state = true;
List<String> contents = code.split('-');
int qrId = int.parse(
contents[1]); //qrコードに埋め込まれたid(urlに設定したid)を取得
ref.read(qrContentsProvider.state).state = qrContentList[
qrId]; //取得したidのqrContentをqrContentListから取得してqrContentsProviderに渡す
if (contents.length <= 2 ||
contents[2] != password) {
//パスワードや、urlのバリデーションを設定することで、
//別のQRコードを読み取ったときにエラーページに行くようにする
await Navigator.of(context)
.pushNamed('/error', arguments: '無効なQRコードです');
}
await Navigator.of(context).pushNamed('/qr_detail');
},
),
),
)
: const Text('読み取り開始ボタンをクリックしてください')
],
),
);
}
}
QRコードをスキャンするページです。
-
ai_barcodeのバーコードを読み取るカメラは、ストップを掛けなければ永遠にカメラが止まらず、スキャンし続けてしまいます。(バグ?仕様?)1度スキャンしたらflagをtrueにしてflagがtrueの場合は処理をストップさせて対処しています。
-
String password = 'password123'
パスワードを設定しています。読み取るQRコードにこのパスワードが含まれていなければ、無効なQRコードと判定されます。これでセキュリティーの対策をしています(ほぼ意味ないが) passwordは自由に設定して大丈夫です。 -
QRコードから取得したidのqrContentをqrContentListから取得してqrContentsProviderに渡しています。
-
パスワードや、urlのバリデーションを設定してあるので、別のQRコードを読み取ったときにはエラーページに行きます。正しいQRコードを読み取った場合は、QRコード詳細ページに遷移します。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_web_qr_code/qr_scan.dart';
import 'package:flutter_web_qr_code/qr_content.dart';
class QRDetailPage extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
QRContent qrContents = ref.watch(qrContentsProvider.state).state;
return Scaffold(
appBar: AppBar(
title: const Text("QRコード詳細"),
), //AppBar
body: Center(
child: Text('${qrContents.id}:${qrContents.name}',
style: const TextStyle(fontSize: 30)),
));
}
}
正しくQRコードが読み取れた場合に遷移するページです。
-
qrコードを読み取ったときに更新した(idに対応するqrContentsを渡しました)qrContentsProviderからqrContentを取得します。
-
取得したqrContentのidとnameを表示させます。ここで正しくQRコードを読み取れているかチェックできます。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_web_qr_code/error.dart';
import 'package:flutter_web_qr_code/qr_scan.dart';
import 'package:flutter_web_qr_code/qr_detail.dart';
void main() {
runApp(
ProviderScope(
child: QRApp(),
),
);
}
class QRApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => QRScanPage(),
'/qr_detail': (BuildContext context) => QRDetailPage(),
'/error': (BuildContext context) => ErrorPage(),
},
title: 'QRコード',
);
}
}
ProviderScopeでriverpodを使えるようにしています。
かんたんなルーティングを設定しています。
QRコードを作成する
こちらで作成します。
[好きなurl]-[id]-[password](例: https://sample-0-password123)でURLに入力し作成するボタンをクリックでQRコードが作成されます。
[好きなurl]
https://qr_sample
にしましょう
[id]
qr_content.dart
のqrContentListのindexになります。qrContentList
の中身を変更していない場合は0~4の数字にしてください。
[password]
qr_scan.dart
のString password =
の値にしてください。
変更していない場合はpassword123
になるはずです。
https://sample-0-password123
で作ってみました。
実行する
flutter run
でbuildしてください。
Multiple devices found:
macOS (desktop) • macos • darwin-arm64 • macOS 12.3.1 21E258 darwin-arm (Rosetta)
Chrome (web) • chrome • web-javascript • Google Chrome 102.0.5005.61
[1]: macOS (macos)
[2]: Chrome (chrome)
Please choose one (To quit, press "q/Q"): 2
Multiple devices found:
と言われた場合は、Chrome(chrome)の数字を入力してください。
最初にqrコードを読み取るカメラが表示されます。写真ではカメラを隠しているので真っ黒ですが、カメラが表示されます。先ほど作成したQRコードをかざしてみましょう。
さきほど作成した(https://sample-0-password123
)QRコードをかざすと、qrContentsListのindexが0にあたる、いぬが表示されました!
qrContentsListやpasswordを編集したり、いろいろなqrCodeを作成してみてください。
リポジトリ
参考
Discussion