qr_scanner_kit
A ready-to-use QR scanner screen and viewfinder overlay built on
mobile_scanner, with torch
control, camera switching, and a simple scan callback. Extracted from the
Rocketbot app so every scanner screen behaves the same.
Features
QrScanner: a full-screen scanner with a live camera preview.- Torch toggle and front/back camera switching in the app bar.
BarcodeOverlay: aShapeBorderthat dims the preview and draws corner brackets around a centered cut-out.ScanGate: a small helper that lets a callback run at most once.- One-shot
onScancallback withpopOnScancontrol. - Optional injection of a
MobileScannerController. - No state-management dependency; plain Flutter widgets.
Install
flutter pub add qr_scanner_kit
Until the package is published on pub.dev, depend on it with a path:
dependencies:
qr_scanner_kit:
path: packages/qr_scanner_kit
Permissions
mobile_scanner needs camera access at runtime. Android permissions are
merged from the plugin manifest and requested by the plugin, while iOS
requires a usage description in the app's Info.plist.
Android
mobile_scanner merges the CAMERA permission from its own manifest and
requests it at runtime, so no app-side change is strictly required. To declare
it explicitly, add it to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.CAMERA" />
If the app handles permission UX itself, for example with permission_handler,
request the permission before pushing QrScanner.
iOS
This entry is required on iOS. Add a usage description to
ios/Runner/Info.plist:
<key>NSCameraUsageDescription</key>
<string>This app needs camera access to scan QR codes.</string>
The system shows this text in the permission prompt, and the app is rejected by App Review without it.
Usage
The snippet below matches example/lib/main.dart:
import 'package:flutter/material.dart';
import 'package:qr_scanner_kit/qr_scanner_kit.dart';
void main() {
runApp(const QrScannerExampleApp());
}
class QrScannerExampleApp extends StatelessWidget {
const QrScannerExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'qr_scanner_kit example',
theme: ThemeData(
colorSchemeSeed: const Color(0xFF9BD41E),
useMaterial3: true,
),
home: const ExampleHomePage(),
);
}
}
class ExampleHomePage extends StatefulWidget {
const ExampleHomePage({super.key});
@override
State<ExampleHomePage> createState() => _ExampleHomePageState();
}
class _ExampleHomePageState extends State<ExampleHomePage> {
String _lastScan = 'No scan yet';
Future<void> _openScanner() async {
await Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
return QrScanner(
title: 'Scan QR code',
onScan: (String value) {
setState(() {
_lastScan = value;
});
},
);
},
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('qr_scanner_kit example')),
body: Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Last scan: $_lastScan', textAlign: TextAlign.center),
const SizedBox(height: 24),
FilledButton.icon(
onPressed: _openScanner,
icon: const Icon(Icons.qr_code_scanner),
label: const Text('Open scanner'),
),
],
),
),
),
);
}
}
API
QrScanner
| Parameter | Type | Default | Description |
|---|---|---|---|
onScan |
ValueChanged<String> |
required | Called with the first successfully decoded non-empty barcode value. |
title |
String? |
'Scan QR code' |
Title shown in the app bar. |
overlay |
BarcodeOverlay? |
BarcodeOverlay(borderColor: red, borderRadius: 10, borderLength: 30, borderWidth: 5) |
Overlay drawn on top of the camera preview. |
backgroundColor |
Color? |
Theme.canvasColor |
Background color of the scanner body. |
popOnScan |
bool |
true |
Whether to attempt to pop the enclosing route after the first scan. |
controller |
MobileScannerController? |
created internally | Camera controller; its autoStart setting is respected. |
Scan behavior
onScan is called at most once per QrScanner instance: only the first
successfully decoded, non-empty barcode is reported, and the barcode stream
subscription is cancelled immediately afterwards. With the default
popOnScan: true the scanner attempts to pop the enclosing route (via
Navigator.maybePop) after that first scan. With popOnScan: false the screen
stays open and the camera preview keeps running, but no further barcodes are
reported. Push a new QrScanner to scan again.
Controller ownership
When no controller is passed, QrScanner creates one with
autoStart: false and starts it after subscribing. A provided controller is
not started blindly: its autoStart flag is respected, the widget still
starts and stops it with the app lifecycle, and the widget disposes the
controller when it is disposed. Do not dispose an injected controller
yourself and do not reuse it after the scanner is closed.
Injecting a controller requires a direct dependency on mobile_scanner and
import 'package:mobile_scanner/mobile_scanner.dart';, because
qr_scanner_kit does not re-export it.
ScanGate
ScanGate implements the one-shot guard as a tiny, testable helper:
tryHandle() returns true once and false on every later call until
reset() is called. It is exported for callers who want the same semantics
in their own barcode handling.
BarcodeOverlay
BarcodeOverlay is a ShapeBorder for use with ShapeDecoration. It dims
the area outside a centered cut-out and draws corner brackets around it.
| Parameter | Type | Default | Description |
|---|---|---|---|
borderColor |
Color |
Colors.red |
Color of the corner brackets. |
borderWidth |
double |
3.0 |
Stroke width of the corner brackets. |
overlayColor |
Color |
translucent black | Color used to dim the area outside the cut-out. |
borderRadius |
double |
0 |
Corner radius of the cut-out and corner brackets. |
borderLength |
double |
40 |
Length of each corner bracket. |
cutOutSize |
double? |
null |
Square cut-out size. Mutually exclusive with cutOutWidth/cutOutHeight. |
cutOutWidth |
double? |
effective default: 250 (or cutOutSize) |
Width of the cut-out. |
cutOutHeight |
double? |
effective default: 250 (or cutOutSize) |
Height of the cut-out. |
cutOutBottomOffset |
double |
0 |
Vertical offset applied to the cut-out. |
Provide either cutOutSize or both cutOutWidth and cutOutHeight, never
both. scale returns a copy with every configured value preserved.
Example
A runnable app that pushes the scanner and shows the last scanned value lives
in example/. The example ships without platform folders, so
generate them locally before running on a device with a camera:
cd example
flutter create . --platforms=android,ios
flutter run
The generated android/ and ios/ folders are not committed.
Screenshot
A screenshot for the pub.dev listing is not included yet. Run the example app to see the default overlay and the app bar controls.
Attribution
BarcodeOverlay is a modified copy of OverlayShape from the
ai_barcode_scanner package
(version 5.2.2, Apache-2.0). It was renamed to BarcodeOverlay, public
documentation was added, default border values were changed, validation and
assert messages were corrected, and scale now preserves the configured
overlay values. The full Apache-2.0 license text is reproduced in
THIRD_PARTY_NOTICES.
The package itself is MIT licensed (see LICENSE); only the
derived BarcodeOverlay remains under Apache-2.0.
Libraries
- qr_scanner_kit
- A ready-to-use QR scanner screen and viewfinder overlay built on mobile_scanner, with torch, camera switching, and a simple scan callback.