qrscan 1.2.2
qrscan: ^1.2.2 copied to clipboard
One-shot Flutter QR/barcode scanner: open camera, first decode, string back. Android CameraX+ZXing, iOS Vision. Not a widget.
Language: English | 中文简体
qrscan #
Open camera. First decode. String back. Close.

dependencies:
qrscan: ^1.2.2
import 'package:qrscan/qrscan.dart' as scanner;
String? code = await scanner.scan();
String? wechat = await scanner.scan(looks: ScanLooks.wechat);
String? alipay = await scanner.scan(looks: ScanLooks.alipay);
This is not an embedded camera widget. Preview / scan window / overlay / torch API / continuous scan → mobile_scanner.
This is not a hardware gun. Keyboard wedge → a TextField. OEM broadcast PDA → pda_scanner.
- Android CameraX 1.4 + ZXing 3.5.3
- iOS AVFoundation + Vision
- Dart 3 / Flutter 3.10+ / Android embedding v2
- Five functions:
scan/scanPhoto/scanPath/scanBytes/generateBarCode
Android #
minSdk 21. Camera permission is requested when scan() runs. Do not add storage permissions.
Host MainActivity must be embedding v2 (FlutterActivity). The scanner follows device orientation.
iOS #
<key>NSCameraUsageDescription</key>
<string>Scan QR codes and barcodes</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Read a code from a photo</string>
Minimum iOS 12. Landscape only if the host already lists it in UISupportedInterfaceOrientations.
Usage #
String? cameraScanResult = await scanner.scan();
String? wechat = await scanner.scan(looks: ScanLooks.wechat);
String? alipay = await scanner.scan(looks: ScanLooks.alipay);
String? branded = await scanner.scan(color: Color(0xFF00C853), hint: '对准条码或二维码');
String? photoScanResult = await scanner.scanPhoto();
String? fromPath = await scanner.scanPath(path);
String? fromBytes = await scanner.scanBytes(bytes);
Uint8List qrPng = await scanner.generateBarCode('https://github.com/wu9007/qrcode_scanner');
| Call | Returns |
|---|---|
scan({color, hint}) |
Decoded string, or null if the user cancels. Camera only — not the gallery. color / hint optional |
scanPhoto() |
System picker → decode, or null if cancelled |
scanPath(path) |
Decode a local file |
scanBytes(bytes) |
Decode image bytes |
generateBarCode(code) |
QR PNG as Uint8List (UTF-8 payload) |
That is the whole API. There is no widget, no scan-area crop, no front-camera switch, no stream, no format filter. Overlay is cosmetic — decode still uses the full frame.
Copy this button #
Drop-in widget (also in example/lib/scan_button.dart):
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:qrscan/qrscan.dart' as scanner;
class ScanButton extends StatelessWidget {
const ScanButton({
super.key,
this.looks = scanner.ScanLooks.wechat,
this.label = '扫一扫',
this.onCode,
});
final scanner.ScanLooks looks;
final String label;
final ValueChanged<String>? onCode;
Future<void> _scan(BuildContext context) async {
try {
final String? code = await scanner.scan(looks: looks);
if (code != null) onCode?.call(code);
} on PlatformException catch (e) {
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(e.code)),
);
}
}
@override
Widget build(BuildContext context) {
return FilledButton(
style: FilledButton.styleFrom(
backgroundColor: looks.color,
foregroundColor: Colors.white,
minimumSize: const Size.fromHeight(48),
),
onPressed: () => _scan(context),
child: Text(label),
);
}
}
// WeChat: ScanButton(looks: ScanLooks.wechat, label: '扫一扫', onCode: ...)
// Alipay: ScanButton(looks: ScanLooks.alipay, label: '扫码', onCode: ...)
// Own: ScanButton(looks: ScanLooks(color: Color(0xFFFF6A00), hint: '对准条码'), ...)
Errors from scan() #
Cancel is null. Real failures throw PlatformException:
| Dart constant | code |
When |
|---|---|---|
CameraAccessDenied |
PERMISSION_NOT_GRANTED |
User denied camera |
CameraStartFailed |
CAMERA_START_FAILED |
No back camera / driver error |
CameraInUse |
CAMERA_IN_USE |
Another app already holds the camera |
NoActivity |
NO_ACTIVITY |
Plugin has no Activity / cannot present |
Decoder #
QR is tried first (with TRY_HARDER), then other 2D, then 1D. A dense QR is not returned as UPC-E. There is no formats: argument — if you must restrict symbologies, use mobile_scanner.
Latin-1 QR (ISO-8859-1, no ECI) is not forced to UTF-8. Byte segments that are valid UTF-8 with high bits stay UTF-8 (typical Chinese QR). generateBarCode writes UTF-8.
Decoder order is covered by tool/decoder-harness (63 fixtures in CI). Album photos that are stored rotated are retried at 90° steps on Android and iOS. iOS also honors EXIF orientation and inverts like Android.
When not to use this #
| You want | Use instead |
|---|---|
| Embedded preview / scan window / overlay / continuous scan / web / desktop | mobile_scanner |
| Only 1D / only QR / a format list | mobile_scanner formats: |
| Camera on a phone or a PDA (rear camera) | this plugin — scan() |
| Industrial PDA laser that broadcasts an Intent | pda_scanner |
| Gun set to keyboard (HID) wedge | Any TextField. No plugin. |
Three mutually exclusive paths: camera one-shot (this plugin, including PDA cameras) · OEM laser broadcast (pda_scanner) · HID keyboard (nothing). Mixing them is how field apps “break”.
0.3 → 0.4 #
| Before | After |
|---|---|
| Android only, embedding v1 leftovers | Android + iOS, embedding v2 |
com.github.leyan95:android-zxingLibrary (JitPack, often missing) |
com.google.zxing:core:3.5.3 + CameraX 1.4 |
| iOS was a TODO | Vision actually scans |
| Storage permissions | Camera + system photo picker |
SDK <3.0.0 |
Dart 3 / Flutter 3.10+ |
| Torch crash on devices with no flash | CameraX enableTorch, toast if unavailable |
Scanner locked portrait (tablets / landscape broken) |
Follows device rotation; overlay recenters |
| Dense QR read as UPC-E | QR (TRY_HARDER) before 1D |
| Latin-1 QR forced to UTF-8 | BYTE_SEGMENTS → ISO-8859-1 unless valid UTF-8 |
| Camera busy / bind fail → black Activity | CAMERA_IN_USE / CAMERA_START_FAILED to Dart |
Public Dart API is unchanged. The two new error constants are optional.
License #
MIT. Created by Shusheng.