Barcode and QR Reader in Flutter

Flutter, being a fast growing platform for building cross platform applications (Web, iOS, Android) has things to explore every other day. Recently I was working to build a fairly simply application for a client that uses the camera to read QR Code and then via a web service validate the QR Code. You would find the details below.

Starting with the dependencies that I used that have to be updated in the pubspec.yaml file under the dependencies.

barcode_scan: ^3.0.1
dio: ^3.0.9
intl: ^0.16.1

barcode_scan will be used for reading the bar codes and dio is for webservices. intl is a dependency of dio.

After doing the above, give permission for Internet and Camera from the Android Manifest file. The file can be found at android/app/src/main/AndroidManifest.xml. Right before the closing tag of </manifest> add the following lines:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET"/>

Firstly start by a new StatefulWidget which I called as the InvoiceScannerScreen. The following libraries have to be imported.

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:async';
import 'dart:io' show Platform;
import 'package:barcode_scan/barcode_scan.dart';
import 'package:dio/dio.dart';

Then we start by declaring some variables that will be used as properties and some updated as per the scan results.

This will contain the result after a scan. The property which we use will be RawContent

ScanResult scanResult;

The following are some properties we set for the camera during a scan.

final _flashOnController = TextEditingController(text: "Flash on");
final _flashOffController = TextEditingController(text: "Flash off");
final _cancelController = TextEditingController(text: "Cancel");

var _aspectTolerance = 0.0;
var _numberOfCameras = 0;
var _selectedCamera = -1;
var _useAutoFocus = true;
var _autoEnableFlash = false;

The below is a variable that will store the result of the API assuming that it will be a string value.

var _apiResult = "";
var _QRValue = "";

This final variable defines the possible barcode types during a scan. As of now, the only one enabled is the qr code. The list can be found under the BarcodeFormat enum.

static final _possibleFormats = BarcodeFormat.values.toList().where((element) => element == BarcodeFormat.qr);
//..removeWhere((e) => e == BarcodeFormat.unknown);

List<BarcodeFormat> selectedFormats = [..._possibleFormats];

Initiating the state by detecting the number of cameras on the device.

@override
initState() {
  super.initState();

  Future.delayed(Duration.zero, () async {
    _numberOfCameras = await BarcodeScanner.numberOfCameras;
    setState(() {});
  });
}

Inside of your build method past the following scaffold

return Scaffold(
  appBar: AppBar(
    title: Text('Barcode Scanner Example'),
    actions: <Widget>[
      IconButton(
        icon: Icon(Icons.camera),
        tooltip: "Scan",
        onPressed: scan,
        )
    ],
    ),
  body: ListView(
    scrollDirection: Axis.vertical,
    shrinkWrap: true,
    children: <Widget>[
      FlatButton(
        child: Text('Scan QR Code'),
        onPressed: scan,
      ),
      //The QR Code
      Text(QRValue),
      //The response got from the result
      Text(_apiResult)
    ]
   ),
  );

The above scaffold has a FlatButton that calls the scan function where most of the actual work is done. Here is the scan function:

Future scan() async {
  try {
    var options = ScanOptions(
      strings: {
        "cancel": _cancelController.text,
        "flash_on": _flashOnController.text,
        "flash_off": _flashOffController.text,
      },
      restrictFormat: selectedFormats,
      useCamera: _selectedCamera,
      autoEnableFlash: _autoEnableFlash,
      android: AndroidOptions(
        aspectTolerance: _aspectTolerance,
        useAutoFocus: _useAutoFocus,
        ),
      );

    var result = await BarcodeScanner.scan(options: options);

    setState(() => scanResult = result);

    //Call the API to validate the Code
    var resp = await verifyValue(scanResult.rawContent);

    setState(() { _apiResult = resp; _QRValue = scanResult.rawContent;});

  } on PlatformException catch (e) {
    var result = ScanResult(
      type: ResultType.Error,
      format: BarcodeFormat.unknown,
      );

    if (e.code == BarcodeScanner.cameraAccessDenied) {
      setState(() {
        result.rawContent = 'The user did not grant the camera permission!';
      });
    } else {
      result.rawContent = 'Unknown error: $e';
    }
    setState(() {
      scanResult = result;
    });
  }
}

You may notice that there is another function call right before the ending of the try statement. That call is made to call an API by passing the value that was just read from the scanner.

    verifyValue(scanResult.rawContent);

This function makes a POST call on the provided API using the dio library. The body of the function is as follows.

dynamic verifyValue(String QRValue) async{

Response resp = Response();
var dio = Dio();

resp = await dio.post(
'<yourapi>', data: {
"QRValue": QRValue
});

return resp.data;
}

This should allow you to scan the QR code. For iOS reference, please refer to the documentation provided by the barcode_scan.

Thanks for reading the article.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s