中文文档 | English
A scan code Flutter plugin, which is a Flutter package for HUAWEI ScanKit SDK.The HUAWEI ScanKit is a powerful library that is easy to use and fast to read.
Scan Kit automatically detects, magnifies, and recognizes barcodes from a distance, and is also able to scan a very small barcode in the same way. It works even in suboptimal situations, such as under dim lighting or when the barcode is reflective, dirty, blurry, or printed on a cylindrical surface. This leads to a high scanning success rate, and an improved user experience.
ScanKit supports 13 major barcode formats (listed as follows). If your app requires only some of the 13 formats, specify the desired formats to speed up barcode scanning.
- 1D barcode formats: EAN-8, EAN-13, UPC-A, UPC-E, Codabar, Code 39, Code 93, Code 128, and ITF-14
- 2D barcode formats: QR Code, Data Matrix, PDF417, and Aztec
Support camera scan code and local picture recognition.
Add the following to ios/Runner/Info.plist
<key>NSCameraUsageDescription</key>
<string>Explain to the user here why you need the permission</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Explain to the user here why you need the permission</string>
Note that replacing the content of the
No configuration required for Android platform!
In Flutter, you need a plugin library for permission handling, here I recommend using another plugin library of mine: flutter_easy_permission, go here for detailed configuration.
Open the ios/Podfile file and add the following code:
target 'Runner' do
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
# Add the library of permissions you need here
pod 'EasyPermissionX/Camera'
pod 'EasyPermissionX/Photo'
end
Then execute the command to install.
import 'package:flutter_scankit/flutter_scankit.dart';
void initState() {
super.initState();
scanKit = ScanKit();
scanKit!.onResult.listen((val) {
debugPrint("scanning result:${val.originalValue} scanType:${val.scanType}");
setState(() {
code = val.originalValue;
});
});
FlutterEasyPermission().addPermissionCallback(
onGranted: (requestCode, perms,perm) {
startScan();
},
onDenied: (requestCode, perms,perm, isPermanent) {});
}
Scan the code:
// Request if no permission
if (!await FlutterEasyPermission.has(perms: _permissions,permsGroup: _permissionGroup)) {
FlutterEasyPermission.request(perms: _permissions,permsGroup: _permissionGroup);
} else {
// Call if you have permission
startScan();
}
Future<void> startScan() async {
try {
await scanKit?.startScan();
} on PlatformException {}
}
For instructions on how to use FlutterEasyPermission
, please refer to this link.
If we are unsure of the type of code, we can leave the scanTypes
parameter unspecified. Of course, you can also choose to specify one or more types, like so:
await scanKit?.startScan(scanTypes: ScanTypes.qRCode.bit);
await scanKit?.startScan(
scanTypes: ScanTypes.qRCode.bit |
ScanTypes.code39.bit |
ScanTypes.code128.bit);
Use ScanKitWidget
as the scanning widget, and ScanKitController
for functions such as switching the flashlight and decoding images. The following is just a simple demonstration, you need to customize the UI according to your own needs:
const boxSize = 200.0;
class CustomView extends StatefulWidget {
const CustomView({Key? key}) : super(key: key);
@override
State<CustomView> createState() => _CustomViewState();
}
class _CustomViewState extends State<CustomView> {
ScanKitController _controller = ScanKitController();
@override
void initState() {
_controller.onResult.listen((result) {
debugPrint(
"scanning result:value=${result.originalValue} scanType=${result.scanType}");
/// Note: Here the pop operation must be delayed.
Future(() {
Navigator.of(context).pop(result.originalValue);
});
});
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
var screenWidth = MediaQuery.of(context).size.width;
var screenHeight = MediaQuery.of(context).size.height;
var left = screenWidth / 2 - boxSize / 2;
var top = screenHeight / 2 - boxSize / 2;
var rect = Rect.fromLTWH(left, top, boxSize, boxSize);
return Scaffold(
body: SafeArea(
child: Stack(
children: [
ScanKitWidget(
controller: _controller,
continuouslyScan: false,
boundingBox: rect),
Align(
alignment: Alignment.topCenter,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: () {
Navigator.of(context).pop();
},
icon: Icon(
Icons.arrow_back,
color: Colors.white,
size: 28,
)),
IconButton(
onPressed: () {
_controller.switchLight();
},
icon: Icon(
Icons.lightbulb_outline_rounded,
color: Colors.white,
size: 28,
)),
IconButton(
onPressed: () {
_controller.pickPhoto();
},
icon: Icon(
Icons.picture_in_picture_rounded,
color: Colors.white,
size: 28,
))
],
),
),
Align(
alignment: Alignment.center,
child: Container(
width: boxSize,
height: boxSize,
decoration: BoxDecoration(
border: Border(
left: BorderSide(color: Colors.orangeAccent, width: 2),
right: BorderSide(color: Colors.orangeAccent, width: 2),
top: BorderSide(color: Colors.orangeAccent, width: 2),
bottom: BorderSide(color: Colors.orangeAccent, width: 2)),
),
),
)
],
),
),
);
}
}
We can use ScanKitDecoder
to achieve more flexible customization of scanning requirements. Here is a simple demonstration combined with the camera plugin:
class _BitmapModeState extends State<BitmapMode> {
CameraController? controller;
StreamSubscription? subscription;
String code = '';
ScanKitDecoder decoder = ScanKitDecoder(photoMode: false, parseResult: false);
@override
void initState() {
availableCameras().then((val) {
List<CameraDescription> _cameras = val;
if (_cameras.isNotEmpty) {
controller = CameraController(_cameras[0], ResolutionPreset.max);
controller!.initialize().then((_) {
if (!mounted) {
return;
}
controller!.startImageStream(onLatestImageAvailable);
setState(() {});
});
}
});
subscription = decoder.onResult.listen((event) async{
if (event is ResultEvent && event.value.isNotEmpty()) {
subscription!.pause();
await stopScan();
if (mounted) {
setState(() {
code = event.value.originalValue;
});
}
} else if (event is ZoomEvent) {
/// set zoom value
}
});
super.initState();
}
void onLatestImageAvailable(CameraImage image) async {
if(image.planes.length == 1 && image.format.group == ImageFormatGroup.bgra8888){
await decoder.decode(image.planes[0].bytes, image.width, image.height);
}else if(image.planes.length == 3){
Uint8List y = image.planes[0].bytes;
Uint8List u = image.planes[1].bytes;
Uint8List v = image.planes[2].bytes;
Uint8List combined = Uint8List(y.length + u.length + v.length);
combined.setRange(0, y.length, y);
combined.setRange(y.length, y.length + u.length, u);
combined.setRange(y.length + u.length, y.length + u.length + v.length, v);
await decoder.decodeYUV(combined, image.width, image.height);
}
}
}
You can view the full code in bitmap_mode.dart
.
Of course, we can also directly read image files and let ScanKit recognize the codes in the images. This requirement is more suitable for custom loading of code maps from the image library:
class _LoadImageState extends State<LoadImage> {
ScanKitDecoder decoder = ScanKitDecoder(scanTypes: ScanTypes.qRCode.bit);
String code = '';
@override
void initState() {
load();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(code,maxLines: 2,),
),
);
}
void load()async{
var myData = await rootBundle.load('assets/qrcode.png');
var result = await decoder.decodeImage(myData.buffer.asUint8List());
if(result.isNotEmpty){
setState(() {
code = result.originalValue;
});
}
}
}
ScanKitEncoder encoder = ScanKitEncoder(200, 200, ScanTypes.qRCode,
backgroundColor: Colors.blue, color: Colors.red,margin: 2);
ImageProvider? _image;
void generate() async {
if(controller.text.isNotEmpty){
var bytes = await encoder.encode(controller.text);
final provider = MemoryImage(bytes);
setState(() {
_image = provider;
});
}
}
For the complete example, see build_bitmap.dart
. Here, the margin
parameter defines the border of the code, with a range of [1, 10]. For more information, please refer to the appendix.
Yes. Non-Huawei Android phones are supported by Scan SDK 1.1.0.300 and later.
Currently, the HMS Core Scan SDK can parse most types of 1D and 2D barcodes, including EAN-8, EAN-13, UPC-A, UPC-E, Codabar, Code 39, Code 93, Code 128, ITF-14, QR code, DataMatrix, PDF417, Aztec Code, and the multi-functional code.
The recognition and parsing algorithms of different APIs are the same.
No cloud function is involved, and no personal data is stored.
Scan Kit processes data only on devices and does not store any personal data, ensuring the security of private data.
The reason can be found in SDK Privacy Statement.
HMS Core Scan SDK 1.2.0.300 and later support phones running iOS 9.0 and later.
Currently, the HMS Core Scan SDK can parse most of 1D and 2D barcodes, including EAN-8, EAN-13, UPC-A, UPC-E, Codabar, Code 39, Code 93, Code 128, ITF-14, QR code, DataMatrix, PDF417, and Aztec Code.
The SDK can run properly, but the error message "Unsupported Architecture. Your executable contains unsupported architecture '[x86_64]..." is displayed when an iOS app is released.
To facilitate debugging, ScanKitFrameWork.framework combines simulator and real device architectures. Before the release, use the lipo tool to remove the related architectures. For details, please refer to the following code:
cd ScanKitFrameWork.framework
# Run the lipo -info command to check the included architectures.
lipo -info ScanKitFrameWork # Architectures in the fat file: ScanKitFrameWork are: x86_64 arm64
# Remove x86_64.
lipo -remove x86_64 ScanKitFrameWork -o ScanKitFrameWork
# Check again.
lipo -info ScanKitFrameWork # Architectures in the fat file: ScanKitFrameWork are: arm64
The Scan SDK integrated in the iOS system does not support the ARMv7 architecture. Therefore, the ARMv7 architecture needs to be removed from the development project.
Integrate the SDK that contains the simulator architecture again. For details, please refer to Integrating the HMS Core SDK.
Recommended barcode color and background
It is recommended that the default setting (black barcode against white background) be used. If white barcode against black background is used, the recognition success rate will decrease.
Recommended border
It is recommended that the default border be used. The value ranges from 1 to 10, in pixels.
Recommended barcode size
Barcode length and content restrictions
Barcode Format | Length Restriction | Content Restriction |
---|---|---|
QR | A maximum of 2953 UTF-8 encoded bytes are supported. | Chinese characters are supported. One Chinese character occupies three bytes. If the content is too long, the barcode will be complex and cannot be easily recognized. |
Aztec | A maximum of 2953 UTF-8 encoded bytes are supported. | Chinese characters are supported. One Chinese character occupies three bytes. If the content is too long, the barcode will be complex and cannot be easily recognized. |
PDF417 | A maximum of 1777 UTF-8 encoded bytes are supported. | Chinese characters are supported. One Chinese character occupies three bytes. If the content is too long, the barcode will be complex and cannot be easily recognized. |
DataMatrix | A maximum of 2335 UTF-8 encoded bytes are supported. | iOS supports Chinese and English, in which one Chinese character occupies three bytes. Android supports only English. If the content is too long, the barcode will be complex and cannot be easily recognized. |
UPC-A | An 11-digit number must be entered. | Only numbers are supported. A barcode in this format is a 12-digit number (with the last digit used for verification). |
UPC-E | A 7-digit number must be entered. | Only numbers are supported. A barcode in this format is an 8-digit number (with the last digit used for verification), and the first digit must be 0 or 1. |
ITF14 | A number with up to 80 digits is allowed and must contain an even number of digits. | Only numbers are supported. A barcode containing an even number of digits is generated. If the content is too long, the barcode will be complex and cannot be easily recognized. |
EAN-8 | A 7-digit number must be entered. | Only numbers are supported. A barcode in this format is an 8-digit number (with the last digit used for verification). |
EAN-13 | A 12-digit number must be entered. | Only numbers are supported. A barcode in this format is a 13-digit number (with the last digit used for verification), and the first digit cannot be 0. If the first digit is not 0 for Android, a UPC-A code is generated. |
Code 39 | A maximum of 80 bytes are supported. | The charset supported contains numbers, uppercase letters, hyphens (-), dots (.), dollar signs ($), slashes (/), plus signs (+), percent signs (%), and spaces. |
Code 93 | A maximum of 80 bytes are supported. | The charset supported contains numbers, uppercase letters, hyphens (-), dots (.), dollar signs ($), slashes (/), plus signs (+), percent signs (%), asterisks (*), and spaces. |
Code 128 | A maximum of 80 bytes are supported. | The charset supported is the same as that in the encoding table. |
Codabar | A maximum of 2953 UTF-8 encoded bytes are supported. | If a barcode in this format starts with letter A, B, C, D, T, N, or E, or with an asterisk (*), the barcode must end with the same character. If the barcode does not start with any of the characters, add letter A to the beginning and end of the barcode. Other characters in the barcode can be numbers, hyphens (-), dots (.), dollar signs ($), slashes (/), colons (:), and plus signs (+). |
For a complete example, please see here.