DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Using Barcodes in iText 7
  • Publishing Flutter Packages to JFrog Artifactory
  • How to Test QR Codes in Your Applications
  • Why Do We Need to Keep Our Builds Green?

Trending

  • Implementing Explainable AI in CRM Using Stream Processing
  • Securing the Future: Best Practices for Privacy and Data Governance in LLMOps
  • Driving DevOps With Smart, Scalable Testing
  • System Coexistence: Bridging Legacy and Modern Architecture
  1. DZone
  2. Coding
  3. Frameworks
  4. Building Multiple Barcode, QR Code and Datamatrix Scanner With Flutter for Inventory Management

Building Multiple Barcode, QR Code and Datamatrix Scanner With Flutter for Inventory Management

The article demonstrates how to build a multiple barcode, QR code, and DataMatrix scanner with Flutter for inventory management on Android and iOS.

By 
Xiao Ling user avatar
Xiao Ling
·
Mar. 30, 23 · Tutorial
Likes (1)
Comment
Save
Tweet
Share
4.3K Views

Join the DZone community and get the full member experience.

Join For Free

Barcode scanning is an essential tool for modern inventory management. It improves accuracy, efficiency, real-time tracking, and cost savings, making it an important part of any effective inventory management system. In this article, we will demonstrate how to build a multiple barcode, QR code, and DataMatrix scanner with Flutter for inventory management on Android and iOS.


Supported Platforms

  • Android
  • iOS

Flutter Dependencies Used for Multi-Code Scanning App

To extend the capabilities of the target Flutter project beyond what is provided by Flutter’s core libraries, third-party Flutter plugins are necessary. The following is a list of the plugins used in this project:

  • dynamsoft_capture_vision_flutter — A Flutter plugin for capturing mobile camera stream and scanning barcodes, QR codes, DataMatrix, and other mainstream 1D/2D barcode symbologies. It is built and maintained by Dynamsoft. You need to apply for a trial license of Dynamsoft Barcode Reader and update the LICENSE-KEY in lib/main.dart to run the project.
  • provider — A wrapper around InheritedWidget. It provides an easy way to share data between widgets in a Flutter application.
  • url_launcher — A Flutter plugin for launching a URL in the mobile platform.
  • share_plus — A Flutter plugin for sharing text and files from the mobile platform. It is built and maintained by fluttercommunity.
  • image_picker — A Flutter plugin for iOS and Android for picking images from the image library and taking new pictures with the camera.
  • flutter_exif_rotation — A Flutter plugin for rotating images based on EXIF data. It fixes the orientation issue of images taken by the camera of some devices.

Steps to Build a Multi-Code Scanner with Flutter

In the following sections, we will walk through the steps of building a multiple barcode, QR code, and DataMatrix scanner with Flutter.

The Home Screen

The home screen consists of two tile buttons, a setting button, and a tab bar. The two tile buttons are used for launching the camera scan and file scan, respectively. The setting button is for changing the barcode types. The tab bar is for switching between the home view, the history view, and the about view.

How To Create a Tile Button

To create a tile button that consists of an icon and a label, you can use the ElevatedButton widget with style property set to a square shape.

Dart
 
ElevatedButton(
  onPressed: () {
    Navigator.push(
      context,
      MaterialPageRoute(
          builder: (context) => const ScannerScreen()),
    );
  },
  style: ElevatedButton.styleFrom(
    minimumSize: const Size.square(
        64), // Set the size of the button to be square
  ),
  child: Stack(
    children: const [
      Align(
        alignment: Alignment.center,
        child: Padding(
          padding: EdgeInsets.all(8.0),
          child: Text(
            'Inventory Scan',
            style: TextStyle(
              color: Colors.white,
              fontSize: 16,
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
      ),
      Align(
        alignment: Alignment.bottomRight,
        child: Padding(
          padding: EdgeInsets.all(8.0),
          child: Icon(
            Icons.camera,
            color: Colors.white,
          ),
        ),
      ),
    ],
  ),


To layout multiple tile buttons, you can use the GridView.count widget with crossAxisCount property set to 2, mainAxisSpacing and crossAxisSpacing properties set to 16, and padding property set to 16.

Dart
 
child: GridView.count(
  crossAxisCount: 2, 
  mainAxisSpacing: 16, 
  crossAxisSpacing: 16, 
  padding: const EdgeInsets.all(16), 


How To Add a Setting Button to the Status Bar

The setting button is used for changing the barcode types. To add a setting button to the status bar, you can use the AppBar widget with actions property set to a IconButton widget.

Dart
 
AppBar(
  title: Text(widget.title),
  actions: [
    IconButton(
      icon: const Icon(Icons.settings),
      onPressed: () async {
        var result = await Navigator.push(
          context,
          MaterialPageRoute(
              builder: (context) => const SettingsScreen()),
        );
      },
    ),
  ],


The result variable is the barcode types returned from the setting screen. You can use the Provider widget to store the value for global access.

How to Share the Barcode Types Between Different Widgets of a Flutter App

Flutter Provider is a state management solution that allows you to easily manage the state of your Flutter app. You can easily share data between different parts of your app without needing to pass it through a chain of callbacks.

In this project, we need to make the multiple barcode types and the barcode detection result accessible to different widgets. The following steps show how to use the Provider widget to share the global state:

  1. Create a ChangeNotifier class to store the barcode types and the barcode detection result.

    Dart
     
     import 'package:dynamsoft_capture_vision_flutter/dynamsoft_capture_vision_flutter.dart';
        import 'package:flutter/foundation.dart';
    
        class ScanProvider extends ChangeNotifier {
          int _types = 0;
    
          int get types => _types;
    
          set types(int value) {
            _types = value;
            notifyListeners();
          }
    
          final Map _results = {};
    
          Map get results => _results;
    
          void addResult(String key, BarcodeResult result) {
            _results[key] = result;
            notifyListeners();
          }
    
          void clearResults() {
            _results.clear();
            notifyListeners();
          }
    
          void removeResult(String key) {
            _results.remove(key);
            notifyListeners();
          }
    


  2. Create a ChangeNotifierProvider widget to wrap the ScanProvider widget, and then add the ScanProvider widget to the MultiProvider widget. The MultiProvider widget can contain multiple ChangeNotifierProvider widgets.

    Dart
     
    void main() {
          runApp(MultiProvider(providers: [
            ChangeNotifierProvider(create: (_) => SwitchProvider()),
            ChangeNotifierProvider(create: (_) => ScanProvider()),
          ], child: const MyApp()));
    


    The SwitchProvider widget will be used to toggle the camera size in the later section.

  3. Save the barcode types to the ScanProvider widget.

    Dart
     
    var result = await Navigator.push(
          context,
          MaterialPageRoute(
              builder: (context) => const SettingsScreen()),
        );
    


How To Create a Tab Bar

A tab bar view allows users to navigate between different views without needing to go back and forth between different screens. To create a tab bar, you can use the TabBarView widget with children property set to a list of Widget objects.

Dart
 
late TabController _tabController;

@override
void initState() {
  super.initState();
  _tabController = TabController(vsync: this, length: 3);
  _initLicense();
}

TabBarView(
  controller: _tabController,
  children: const [
    HomeView(title: 'Dynamsoft Barcode SDK'),
    HistoryView(title: 'History'),
    InfoView(title: 'About the SDK'),
  ],


The Barcode Type Setting Screen

The setting screen is used to configure Dynamsoft Barcode Reader. Currently, only the barcode symbology is supported. You can also add other parameters for tuning the SDK performance.

Dart
 
class _SettingsScreenState extends State {
  bool _is1dChecked = true;
  bool _isQrChecked = true;
  bool _isPdf417Checked = true;
  bool _isDataMatrixChecked = true;

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        int format = 0;
        if (_is1dChecked) {
          format |= EnumBarcodeFormat.BF_ONED;
        }
        if (_isQrChecked) {
          format |= EnumBarcodeFormat.BF_QR_CODE;
        }
        if (_isPdf417Checked) {
          format |= EnumBarcodeFormat.BF_PDF417;
        }
        if (_isDataMatrixChecked) {
          format |= EnumBarcodeFormat.BF_DATAMATRIX;
        }
        Navigator.pop(context, {'format': format});
        return true;
      },
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Settings'),
        ),
        body: ListView(
          children: [
            CheckboxListTile(
              title: const Text('1D Barcode'),
              value: _is1dChecked,
              onChanged: (bool? value) {
                setState(() {
                  _is1dChecked = value!;
                });
              },
            ),
            CheckboxListTile(
              title: const Text('QR Code'),
              value: _isQrChecked,
              onChanged: (bool? value) {
                setState(() {
                  _isQrChecked = value!;
                });
              },
            ),
            CheckboxListTile(
              title: const Text('PDF417'),
              value: _isPdf417Checked,
              onChanged: (bool? value) {
                setState(() {
                  _isPdf417Checked = value!;
                });
              },
            ),
            CheckboxListTile(
              title: const Text('DataMatrix'),
              value: _isDataMatrixChecked,
              onChanged: (bool? value) {
                setState(() {
                  _isDataMatrixChecked = value!;
                });
              },
            ),
          ],
        ),
      ),
    );
  }


The WillPopScope widget is used to intercept the back button event. When the back button is pressed, the barcode types are returned as a JSON object.

Camera Preview and Real-Time Barcode Scanning

camera preview full screen

The Flutter plugin of Dynamsoft Barcode Reader helps developers integrate barcode scanning functionality into their Flutter apps with a few lines of Dart code. The plugin supports detecting multi-code from a single image and real-time camera stream.

To use an iOS camera, you need to add the following descriptions to the ios/Runner/Info.plist file before writing any code.
NSCameraUsageDescription
Can I use the camera please?
NSMicrophoneUsageDescription
Can I use the mic please?
NSPhotoLibraryUsageDescription
Load document images from gallery

The Flutter barcode scanner plugin can be used as follows:

  1. Initialize the SDK.

    Dart
     
    final DCVCameraView _cameraView = DCVCameraView();
        late final DCVCameraEnhancer _cameraEnhancer;
        late final DCVBarcodeReader _barcodeReader;
        late ScanProvider _scanProvider;
    
        @override
        void initState() {
          super.initState();
          WidgetsBinding.instance.addObserver(this);
          _sdkInit();
        }
    
        Future _sdkInit() async {
          _scanProvider = Provider.of(context, listen: false);
    
          _barcodeReader = await DCVBarcodeReader.createInstance();
          _cameraEnhancer = await DCVCameraEnhancer.createInstance();
    
          DBRRuntimeSettings currentSettings =
              await _barcodeReader.getRuntimeSettings();
    
          if (_scanProvider.types != 0) {
            currentSettings.barcodeFormatIds = _scanProvider.types;
          } else {
            currentSettings.barcodeFormatIds = EnumBarcodeFormat.BF_ALL;
          }
          currentSettings.expectedBarcodeCount = 0;
          await _barcodeReader
              .updateRuntimeSettingsFromTemplate(EnumDBRPresetTemplate.DEFAULT);
          await _barcodeReader.updateRuntimeSettings(currentSettings);
          _cameraView.overlayVisible = true;
    
          _cameraView.torchButton = TorchButton(
            visible: true,
          );
    
          await _barcodeReader.enableResultVerification(true);
    
          _barcodeReader.receiveResultStream().listen((List? res) {
            if (mounted) {
              decodeRes = res ?? [];
              String msg = '';
              for (var i = 0; i < decodeRes.length; i++) {
                msg += '${decodeRes[i].barcodeText}\n';
    
                if (_scanProvider.results.containsKey(decodeRes[i].barcodeText)) {
                  continue;
                } else {
                  _scanProvider.results[decodeRes[i].barcodeText] = decodeRes[i];
                }
              }
    
              setState(() {});
            }
          });
    
          start();
    


    The image processing and barcode decoding are performed in the native code. Thus, the performance is better than some plugins that return the image data to the Dart code for processing. You can easily receive the barcode results by listening to the receiveResultStream().

  2. Start and stop the camera.

    Dart
     
    Future stop() async {
          await _cameraEnhancer.close();
          await _barcodeReader.stopScanning();
        }
    
        Future start() async {
          _isCameraReady = true;
          setState(() {});
    
          Future.delayed(const Duration(milliseconds: 100), () async {
            _cameraView.overlayVisible = true;
            await _barcodeReader.startScanning();
            await _cameraEnhancer.open();
          });
    


    The Future.delayed() is used to guarantee the camera view widget is ready before starting the barcode scanning.

  3. Create the layout that contains the camera view and the result view.

    Dart
     
    Widget createSwitchWidget(bool switchValue) {
          if (!_isCameraReady) {
            // Return loading indicator if camera is not ready yet.
            return const Center(
              child: CircularProgressIndicator(),
            );
          }
          if (switchValue) {
            return Stack(
              children: [
                Container(
                  color: Colors.white,
                ),
                Container(
                  height: MediaQuery.of(context).size.height -
                      200 -
                      MediaQuery.of(context).padding.top,
                  color: Colors.white,
                  child: Center(
                    child: createListView(context),
                  ),
                ),
                if (_isScanning)
                  Positioned(
                    top: 0,
                    right: 20,
                    child: SizedBox(
                      width: 160,
                      height: 160,
                      child: _cameraView,
                    ),
                  ),
                Positioned(
                  bottom: 50,
                  left: 50,
                  right: 50,
                  child: SizedBox(
                      width: MediaQuery.of(context).size.width * 0.5,
                      height: 64,
                      child: Row(
                        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                        children: [
                          ElevatedButton(
                            onPressed: () {
                              if (_isScanning) {
                                _isScanning = false;
                                stop();
                                _scanButtonText = 'Start Scanning';
                                setState(() {});
                              } else {
                                _isScanning = true;
                                _scanButtonText = 'Stop Scanning';
                                start();
                              }
                            },
                            child: Text(_scanButtonText),
                          ),
                          Center(
                            child: IconButton(
                              icon: const Icon(Icons.flash_on),
                              onPressed: () {
                                if (_isFlashOn) {
                                  _isFlashOn = false;
                                  _cameraEnhancer.turnOffTorch();
                                } else {
                                  _isFlashOn = true;
                                  _cameraEnhancer.turnOnTorch();
                                }
                              },
                            ),
                          ),
                        ],
                      )),
                ),
              ],
            );
          } else {
            return Stack(
              children: [
                Container(
                  child: _cameraView,
                ),
                SizedBox(
                  height: 100,
                  child: ListView.builder(
                    itemBuilder: listItem,
                    itemCount: decodeRes.length,
                  ),
                ),
                Positioned(
                    bottom: 50,
                    left: 50,
                    right: 50,
                    child: SizedBox(
                      width: 64,
                      height: 64,
                      child: ElevatedButton(
                        onPressed: () {
                          Navigator.push(
                            context,
                            MaterialPageRoute(
                                builder: (context) => const HistoryView(
                                      title: 'Scan Results',
                                    )),
                          );
                        },
                        child: const Text('Show Results'),
                      ),
                    ))
              ],
            );
          }
        }


    By default, the camera view is full of the screen. When pressing the switch button located in the top right corner, the camera view will resize to a smaller window and hover on the result view. The resulting view is a list view that displays the barcode results.

Read Barcode, QR Code, and DataMatrix from Image Files

The image_picker plugin allows you to select an image from the gallery or take a picture with the camera. Here is the code snippet:

Dart
 
onPressed: () async {
  XFile? pickedFile =
                  await _imagePicker.pickImage(source: ImageSource.gallery);
  XFile? pickedFile =
      await _imagePicker.pickImage(source: ImageSource.camera);
},


Once an image is selected, you need to use FlutterExifRotation.rotateImage to rotate the image to the correct orientation. Otherwise, the coordinates of the barcode may be incorrect.

Dart
 
if (pickedFile != null) {
  final rotatedImage = await FlutterExifRotation.rotateImage(
      path: pickedFile.path);
  _file = rotatedImage.path;
  _results = await _barcodeReader.decodeFile(_file!) ?? [];
  for (var i = 0; i < _results.length; i++) {
    if (_scanProvider.results
        .containsKey(_results[i].barcodeText)) {
      continue;
    } else {
      _scanProvider.results[_results[i].barcodeText] =
          _results[i];
    }
  }


The decodeFile() method is used to decode the barcode from the image file. The result is a list of BarcodeResult objects. You can use the barcodeText property to get the barcode value.

The Result View

The resulting view is a list view that displays the barcode results. The ListView.builder is used to create the list view.

Dart
 
Widget createListView(BuildContext context) {
  ScanProvider scanProvider = Provider.of(context);
  return ListView.builder(
      itemCount: scanProvider.results.length,
      itemBuilder: (context, index) {
        return ListTile(
          title: createURLString(
              scanProvider.results.values.elementAt(index).barcodeText),
          subtitle: Text(
              scanProvider.results.values.elementAt(index).barcodeFormatString),
        );
      });


If the result is a valid HTTP or HTTPS URL, you can use the launchUrlString() function to open it. Additionally, you can enable long press event monitoring on the list item to provide users with the option to share the barcode result.

Dart
 
Widget createURLString(String text) {
  // Create a regular expression to match URL strings.
  RegExp urlRegExp = RegExp(
    r'^(https?|http)://[^\s/$.?#].[^\s]*$',
    caseSensitive: false,
    multiLine: false,
  );

  if (urlRegExp.hasMatch(text)) {
    return InkWell(
      onLongPress: () {
        Share.share(text, subject: 'Scan Result');
      },
      child: Text(
        text,
        style: const TextStyle(color: Colors.blue),
      ),
      onTap: () async {
        launchUrlString(text);
      },
    );
  } else {
    return InkWell(
      onLongPress: () async {
        Share.share(text, subject: 'Scan Result');
      },
      child: Text(text),
    );
  }


Run the Multi-Code Scanner on Android and iOS

flutter run

Source Code

https://github.com/yushulx/multiple-barcode-qrcode-datamatrix-scan

Barcode QR code Flutter (software) Software build

Published at DZone with permission of Xiao Ling. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Using Barcodes in iText 7
  • Publishing Flutter Packages to JFrog Artifactory
  • How to Test QR Codes in Your Applications
  • Why Do We Need to Keep Our Builds Green?

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!