This package allows the recognition of optical encoded information related to SBB use cases. It is built on top of google_mlkit and runs solely on-device.

Table Of Contents

Click to expand

Getting-Started

Supported platforms

Android badge iOS badge

Preconditions

This package uses the Flutter Camera plugin to access the device camera. Make sure to follow their installation instructions.

Futhermore, follow the instructions on google_mlkit_barcode_scanning and google_mlkit_text_recognition for proper setup. Specifically, setup the minimum iOS and Android SDKs and the Podfile.

In Code Usage

After importing the library, you can add a DataScanner to your widget tree. You can control its size by wrapping it in a SizedBox for example. If there are no size constraints (e.g. in a Row or Column), the DataScanner will not exceed the screen's width and/or height (depending on orientation).

SizedBox(
  height: 250,
  width: 400,
  child: DataScanner( /* ... */ ),
)

If you want a full-page scanner, you can use the DataScannerPage widget instead.

Navigator.of(context).push(
  MaterialPageRoute(
    builder: (context) => DataScannerPage( /* ... */ ),
  ),
);

Documentation

Features

Feature iOS Android
Optical Character Recognition :white_check_mark: :white_check_mark:
Barcode Scanning :white_check_mark: :white_check_mark:
QR Code Scanning :white_check_mark: :white_check_mark:
UIC Number recognition :white_check_mark: :white_check_mark:

DataScanner Configuration

You can configure the DataScanner by passing a DataScannerConfiguration<T> into it, where T stands for whichever type a successful extraction will return. This will be covered in detail in the Extractors section, but is commonly inferred by the compiler. Almost all fields have default values, the exceptions being the required values processor and extractor which will be covered in their respective sections.

DataScanner(
  scannerConfiguration: DataScannerConfiguration<T>(
    processors: // Covered in their own section
    extractors: // Covered in their own section

    routeObserver: RouteObserver<ModalRoute>(),

    onExtracted: (T value) {
      // Called for every extracted value which is
      // a) inside the detection area,
      // b) not null, and
      // c) not equal to the previously extracted value
    },

    onPermissionDenied: () {
      // Called when the app does not have camera permissions
    },

    onError: (String error) {
      // Called when the library encounters any other camera error
    },

    showTorchToggle: true,
    torchToggleMargin: EdgeInsets.all(32),
    torchToggleAlignment: Alignment.bottomCenter,

    showOverlay: true,
    upperHelper: Text('Appears above the detection area'),
    lowerHelper: Text('Appears below the detection area'),

    detectionAreaHeight: 75,
    detectionAreaWidth: 250,
    detectionAreaMode: DetectionAreaMode.containsRect,

    detectionOutline: DetectionOutlineConfig(
      margin: 5,
      padding: 10,

      activeOutlineColor: Colors.green,
      inactiveOutlineColor: Colors.grey,
      outlineWidth: 2,
      cornerRadius: 5,

      enableLabel: true,
      labelColor: Colors.white,
      labelSize: 12,
      labelPosition: DetectionLabelPosition.bottomLeft,
    ),

    enableZoom: true,
    onZoomChanged: (double zoom) {
      // is called if zoom changes with a minimum change sensitivity of 0.01
    }
  ),
)

Vision Processors

The purpose of vision processors is to detect elements in an image and return their value and position relative to an origin point (usually the top left corner). The library ships with three preconfigured vision processors, all based on Google ML Kit:

Processor Name Note
TextRecognitionVisionProcessor Recognize text in images. Only supports the Latin script. Please contact a maintainer for other scripts.
OneDimensionalBarcodeVisionProcessor This extractor extracts the value of UIC numbers and provides information and detailed descriptions for it.
TwoDimensionalBarcodeVisionProcessor This extractor extracts the value of GS1 codes and provides information and detailed descriptions for it.

Add a list of processor(s) to the DataScannerConfiguration processors argument:

DataScanner(
  scannerConfiguration: DataScannerConfiguration(
    // ...
    processors: [
      TextRecognitionVisionProcessor(),
      // OneDimensionalBarcodeVisionProcessor(),
      // TwoDimensionalBarcodeVisionProcessor()
    ],
  ),
)

Additionally, it is possible create your own custom vision processor by implementing the VisionProcessor interface. This allows the library to also detect elements other than text, QR-codes and barcodes:

abstract class VisionProcessor {
  Future<Map<Rect, String?>> getBoundingBoxes({
    required CameraImage image,
    required int imageRotation,
  });
}

Extractors

An extractor takes the values detected by the vision processor, and tries to extract data in a certain format (like URLs, UIC Numbers or GS1 codes). At the same time, it also works as a filter, since detected values which to not match the given format are dismissed. The library ships with three preconfigured extractors, which can be supplied to the DataScannerConfiguration via the extractors parameter.

Extractor Name Return Type (<T> of DataScannerConfiguration) Note
RawTextExtractor String? Only for unit testing
UICDetailsExtractor UICDetails? This extractor extracts the value of UIC numbers and provides information and detailed descriptions for it.
GS1DetailsExtractor GS1Details? This extractor extracts the value of GS1 codes and provides information and detailed descriptions for it.

Exemplary UICDetailsExtractor implementation:

DataScanner(
  scannerConfiguration: DataScannerConfiguration<UICDetails>(
    // ...
    extractors: [
      UICDetailsExtractor( /* ... */ ),
    ],
  ),
)
Custom Extractors

But what about URLs, phone numbers, and brands owned by Nestlé, etc.? Fear not, it's quite simple to extract these kind of formats. Simply implement the Extractor<T> interface, and you're good to go.

abstract class Extractor<T> {
  T? extract(String? input);
}

The extract method must return a nullable type. So, when do we return null? It's simple: When the value cannot be extracted due to formatting, missing components in input, or an error. This causes the ScannerConfiguration.onExracted to not be fired, as the detected value has an invalid format.

For example, here is a simple CompoundSentenceExtractor which only extracts values which are a compund sentence, as indicated by a comma and trailing period.

class CompoundSentenceExtractor implements Extractor<String> {
  String? extract(String? input) {
    if (input == null) return null;

    final containsComma = input.contains(',');
    final isSentence = input.endsWith('.');

    // Why yes, of course ",." is a valid compound sentence!
    return containsComma && isSentence ? input : null;
  }
}

You can now use your own extractor:

DataScanner(
  scannerConfiguration: DataScannerConfiguration<String>(
    // ...
    extractors: [
      CompoundSentenceExtractor(),
    ],
  ),
)

License

This project is licensed under MIT.

Contributing

This repository includes a CONTRIBUTING.md file that outlines how to contribute to the project, including how to submit bug reports, feature requests, and pull requests.

Maintainer

Credits

In addition to the contributors on Github, we thank the following people for their work on previous versions:

  • Ulrich Raab (previous maintainer)

Coding Standards

See CODING_STANDARDS.md.

Code of Conduct

See CODE_OF_CONDUCT.md.