flutter_ocr_sdk and flutter_barcode_sdk are two Flutter plugins built on top of the Dynamsoft Capture Vision SDK. They provide easy-to-use, cross-platform APIs for adding MRZ recognition and barcode scanning capabilities to Flutter apps. In this article, you'll learn how to create a Flutter app that integrates both plugins to scan machine-readable zones (MRZ) and 1D/2D barcodes. Instead of starting from scratch, we'll combine two existing example projects into a unified solution.
Demo Video: Flutter MRZ and Barcode Scanner
Prerequisites
- Get a free trial license key for Dynamsoft Capture Vision SDK.
- Download the two example projects: Flutter MRZ Scanner and Flutter Barcode Scanner. We'll merge these projects to create a new app.
Steps to Build an MRZ and Barcode Scanner App in Flutter
The app will include:
- A toggle button to switch between MRZ and Barcode scanning modes
- A button to load an image file for detection
- A button to open the camera for live detection
- A camera view for real-time MRZ and 1D/2D barcode scanning
- A result view to display scan results from camera streams and images
We'll start by modifying the source code from the Flutter MRZ Scanner project to add UI and functionality.
Step 1: Install Dependencies
Add the following dependencies to your pubspec.yaml file:
flutter_barcode_sdk: ^3.0.4
flutter_ocr_sdk: ^2.0.4
Step 2: Initialize the SDKs
Since both plugins wrap the same Dynamsoft SDK, you can initialize them with a shared license key in global.dart.
FlutterOcrSdk detector = FlutterOcrSdk();
FlutterBarcodeSdk barcodeReader = FlutterBarcodeSdk();
bool isLicenseValid = false;
Future<int> initSDK() async {
  String licenseKey =
      "LICENSE-KEY";
  int? ret = await detector.init(licenseKey);
  ret = await barcodeReader.init();
  ret = await barcodeReader.setLicense(licenseKey);
  if (ret == 0) isLicenseValid = true;
  return await detector.loadModel(modelType: ModelType.mrz) ?? -1;
}
Step 3: Toggle MRZ and Barcode Modes
In home_page.dart, update the toggle button to switch between MRZ and Barcode modes:
ToggleButtons(
  borderRadius: BorderRadius.circular(10),
  isSelected: [isMrzSelected, !isMrzSelected],
  selectedColor: Colors.white,
  fillColor: Colors.orange,
  color: Colors.grey,
  children: const [
    Padding(
      padding: EdgeInsets.symmetric(horizontal: 20),
      child: Text('MRZ'),
    ),
    Padding(
      padding: EdgeInsets.symmetric(horizontal: 20),
      child: Text('Barcode'),
    ),
  ],
  onPressed: (index) {
    setState(() {
      isMrzSelected = (index == 0);
    });
  },
)
The global boolean variable isMrzSelected determines which scanning mode is active.
Step 4: Scan from Image File
Update the scanImage() method in home_page.dart to perform MRZ or barcode recognition depending on the selected mode:
void openMrzResultPage(OcrLine information) {
  Navigator.push(
    context,
    MaterialPageRoute(
      builder: (context) => MrzResultPage(information: information),
    ),
  );
}
void openBarcodeResultPage(List<BarcodeResult> barcodeResults) {
  Navigator.push(
    context,
    MaterialPageRoute(
      builder: (context) => BarcodeResultPage(barcodeResults: barcodeResults),
    ),
  );
}
void scanImage() async {
  XFile? photo = await picker.pickImage(source: ImageSource.gallery);
  if (photo == null) {
    return;
  }
  if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
    File rotatedImage = await FlutterExifRotation.rotateImage(
      path: photo.path,
    );
    photo = XFile(rotatedImage.path);
  }
  Uint8List fileBytes = await photo.readAsBytes();
  ui.Image image = await decodeImageFromList(fileBytes);
  ByteData? byteData = await image.toByteData(
    format: ui.ImageByteFormat.rawRgba,
  );
  if (byteData != null) {
    if (isMrzSelected) {
      List<List<OcrLine>>? results = await detector.recognizeBuffer(
        byteData.buffer.asUint8List(),
        image.width,
        image.height,
        byteData.lengthInBytes ~/ image.height,
        ImagePixelFormat.IPF_ARGB_8888.index,
        ImageRotation.rotation0.value,
      );
      if (results != null && results[0].isNotEmpty) {
        openMrzResultPage(results[0][0]);
      } else {
        showAlert(context, "OCR Result", "Recognition Failed!");
      }
    } else {
      List<BarcodeResult>? results = await barcodeReader.decodeImageBuffer(
        byteData.buffer.asUint8List(),
        image.width,
        image.height,
        byteData.lengthInBytes ~/ image.height,
        ImagePixelFormat.IPF_ARGB_8888.index,
      );
      if (results.isNotEmpty) {
        openBarcodeResultPage(results);
      } else {
        showAlert(context, "Barcode Result", "Detection Failed!");
      }
    }
  }
}
Both result pages already exist in the sample projects. Rename them to avoid conflicts.
Step 5: Real-time Scanning
In camera_manager.dart, update functions like webCamera(), processId(), and _decodeFrame() to call the appropriate APIs based on isMrzSelected. This enables real-time MRZ and barcode scanning across platforms.
Future<void> webCamera() async {
  _isWebFrameStarted = true;
  try {
    while (!(controller == null || isFinished || cbIsMounted() == false)) {
      XFile file = await controller!.takePicture();
      dynamic results;
      if (isMrzSelected) {
        results = await detector.recognizeFile(file.path);
        ocrLines = results;
      } else {
        results = await barcodeReader.decodeFile(file.path);
        barcodeResults = results;
      }
      if (results == null || !cbIsMounted()) return;
      cbRefreshUi();
      if (isReadyToGo && results != null) {
        handleResults(results);
      }
    }
  } catch (e) {
    print(e);
  }
  _isWebFrameStarted = false;
}
Future<void> processId(
      Uint8List bytes, int width, int height, int stride, int format) async {
  int rotation = 0;
  bool isAndroidPortrait = false;
  if (MediaQuery.of(context).size.width <
      MediaQuery.of(context).size.height) {
    if (Platform.isAndroid) {
      rotation = OCR.ImageRotation.rotation90.value;
      isAndroidPortrait = true;
    }
  }
  dynamic results;
  if (isMrzSelected) {
    ocrLines = await detector.recognizeBuffer(
        bytes, width, height, stride, format, rotation);
    results = ocrLines;
  } else {
    barcodeResults = await barcodeReader.decodeImageBuffer(
        bytes, width, height, stride, format);
    if (isAndroidPortrait &&
        barcodeResults != null &&
        barcodeResults!.isNotEmpty) {
      barcodeResults =
          rotate90barcode(barcodeResults!, previewSize!.height.toInt());
    }
    results = barcodeResults;
  }
  _isScanAvailable = true;
  if (results == null || !cbIsMounted()) return;
  cbRefreshUi();
  if (isReadyToGo && results != null) {
    handleResults(results!);
  }
}
Future<void> _decodeFrame(Uint8List rgb, int width, int height) async {
  if (isDecoding) return;
  isDecoding = true;
  dynamic results;
  if (isMrzSelected) {
    ocrLines = await detector.recognizeBuffer(
        rgb,
        width,
        height,
        width * 3,
        ImagePixelFormat.IPF_RGB_888.index,
        OCR.ImageRotation.rotation0.value);
    results = ocrLines;
  } else {
    barcodeResults = await barcodeReader.decodeImageBuffer(
        rgb, width, height, width * 3, ImagePixelFormat.IPF_RGB_888.index);
    results = barcodeResults;
  }
  if (cbIsMounted()) {
    cbRefreshUi();
    if (isReadyToGo && results != null) {
      handleResults(results!);
    }
  }
  isDecoding = false;
}
Step 6: Display Scan Overlays
To render overlays for MRZ and barcode results:
- 
Update createCameraPreview()incamera_page.dart:
 List<Widget> createCameraPreview() { Positioned widget; if (isMrzSelected) { widget = Positioned( top: 0.0, right: 0.0, bottom: 0, left: 0.0, child: createOverlay(_cameraManager.ocrLines), ); } else { widget = Positioned( top: 0.0, right: 0.0, bottom: 0, left: 0.0, child: createOverlay(_cameraManager.barcodeResults), ); } if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) { return [ SizedBox(width: 640, height: 480, child: _cameraManager.getPreview()), widget, ]; } else { if (_cameraManager.controller != null && _cameraManager.previewSize != null) { double width = _cameraManager.previewSize!.width; double height = _cameraManager.previewSize!.height; if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { if (MediaQuery.of(context).size.width < MediaQuery.of(context).size.height) { width = _cameraManager.previewSize!.height; height = _cameraManager.previewSize!.width; } } return [ SizedBox( width: width, height: height, child: _cameraManager.getPreview(), ), widget, ]; } else { return [const CircularProgressIndicator()]; } } }
- 
Add overlay rendering logic for barcodes in global.dart:
 class OverlayPainter extends CustomPainter { List<List<OcrLine>>? ocrResults; List<BarcodeResult>? barcodeResults; OverlayPainter(dynamic results) { if (results is List<List<OcrLine>>) { ocrResults = results; } else if (results is List<BarcodeResult>) { barcodeResults = results; } } @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.blue ..strokeWidth = 5 ..style = PaintingStyle.stroke; final textStyle = TextStyle( color: Colors.red, fontSize: 16, ); if (ocrResults != null) { for (List<OcrLine> area in ocrResults!) { for (OcrLine line in area) { canvas.drawLine(Offset(line.x1.toDouble(), line.y1.toDouble()), Offset(line.x2.toDouble(), line.y2.toDouble()), paint); canvas.drawLine(Offset(line.x2.toDouble(), line.y2.toDouble()), Offset(line.x3.toDouble(), line.y3.toDouble()), paint); canvas.drawLine(Offset(line.x3.toDouble(), line.y3.toDouble()), Offset(line.x4.toDouble(), line.y4.toDouble()), paint); canvas.drawLine(Offset(line.x4.toDouble(), line.y4.toDouble()), Offset(line.x1.toDouble(), line.y1.toDouble()), paint); final textSpan = TextSpan( text: line.text, style: textStyle, ); final textPainter = TextPainter( text: textSpan, textAlign: TextAlign.left, textDirection: TextDirection.ltr, ); textPainter.layout(); final offset = Offset( line.x1.toDouble(), line.y1.toDouble(), ); textPainter.paint(canvas, offset); } } } if (barcodeResults != null) { for (var result in barcodeResults!) { double minX = result.x1.toDouble(); double minY = result.y1.toDouble(); if (result.x2 < minX) minX = result.x2.toDouble(); if (result.x3 < minX) minX = result.x3.toDouble(); if (result.x4 < minX) minX = result.x4.toDouble(); if (result.y2 < minY) minY = result.y2.toDouble(); if (result.y3 < minY) minY = result.y3.toDouble(); if (result.y4 < minY) minY = result.y4.toDouble(); canvas.drawLine(Offset(result.x1.toDouble(), result.y1.toDouble()), Offset(result.x2.toDouble(), result.y2.toDouble()), paint); canvas.drawLine(Offset(result.x2.toDouble(), result.y2.toDouble()), Offset(result.x3.toDouble(), result.y3.toDouble()), paint); canvas.drawLine(Offset(result.x3.toDouble(), result.y3.toDouble()), Offset(result.x4.toDouble(), result.y4.toDouble()), paint); canvas.drawLine(Offset(result.x4.toDouble(), result.y4.toDouble()), Offset(result.x1.toDouble(), result.y1.toDouble()), paint); TextPainter textPainter = TextPainter( text: TextSpan( text: result.text, style: const TextStyle( color: Colors.yellow, fontSize: 22.0, ), ), textAlign: TextAlign.center, textDirection: TextDirection.ltr, ); textPainter.layout(minWidth: 0, maxWidth: size.width); textPainter.paint(canvas, Offset(minX, minY)); } } } @override bool shouldRepaint(OverlayPainter oldDelegate) => true; }
Running the Flutter MRZ/Barcode Scanner App
Run the app on Windows, Linux, Android, iOS, and Web:
flutter run -d chrome    # Web
flutter run -d linux     # Linux
flutter run -d windows   # Windows
flutter run              # Android or iOS
Source Code
https://github.com/yushulx/flutter-barcode-mrz-document-scanner/tree/main/examples/barcode_mrz
 


 
    
Top comments (0)