Integracion APP

Scandit: Matrix Scan integration

Within the possibilities offered by Scandit, Matrix Scan is its strong point. Allows scanning multiple barcodes simultaneously. For each frame of the camera, n codes are detected and Scandit notifies us, with a set.

Matrix Scan also offers the possibility of including augmented reality but I will cover it in the next article.

It will be common for customers to want to combine Matrix Scan with Barcode Scanning, usually at view level. This is so because in business logic it is common for there to be user stories where scanning sequentially makes more sense.

So our goal is to add Matrix Scan to the code built in previous article ("Xamarin Forms integration of the SDK"), where we discussed Barcode Scanning, without breaking anything!

Scandit's impediments. We started well...

After many hours of sweat, blood and tears; I think I can make these statements:

  • The Scandit SDK does not allow two or more DataCaptureContext instances to exist for the same license.
    • This would be great to be able to dedicate one to Barcode Scanning and another to Matrix Scan but it is not possible.
  • An instance of DataCaptureContext does not allow objects for Barcode Scanning and Matrix Scan to be created at the same time.
  • To date, in the Scandit documentation there is no example for Xamarin Forms where Barcode Scanning and Matrix Scan coexist.

So now what? We are left with an alternative. We can make our ScanditManager singleton work with both modes by resetting the context with each mode change.

Implemented Matrix Scan in ScanditManager.

We are going to need:

  • An enumeration to define the two Scandit modes.
  • A BarcodeTracking property.
  • A BarcodeTrackingSettings property.
  • A property of the enumeration defined above.
  • A method to change modes.

This is the result:

public enum ScanditMode

{

    BarcodeScanning,

    MatrixScan

}

public sealed class ScanditManager

{

    #region singleton stuff

    private static readonly Lazy<ScanditManager> lazyInstance = new(

        () => new ScanditManager(),

        LazyThreadSafetyMode.PublicationOnly

    );

    public static ScanditManager Instance => lazyInstance.Value;

    private ScanditManager() { }

    #endregion

    private string licence;

    private readonly ISet<Symbology> symbologies = new HashSet<Symbology>

    {

        Symbology.Code128,

        Symbology.Gs1Databar,

        Symbology.Qr

    };

    public bool IsInitialized { get; private set; } = false;

    public ScanditMode CurrentScanditMode { get; private set; }

    public DataCaptureContext Context { get; private set; }

    public Camera Camera { get; private set; } = Camera.GetCamera(CameraPosition.WorldFacing);

    public CameraSettings CameraSettings { get; } = BarcodeCapture.RecommendedCameraSettings;

    //For Barcode Scanning

    public BarcodeCapture BarcodeCapture { get; private set; }

    public BarcodeCaptureSettings BarcodeCaptureSettings { get; private set; }

    //For Matrix Scan

    public BarcodeTracking BarcodeTracking { get; private set; }

    public BarcodeTrackingSettings BarcodeTrackingSettings { get; private set; }

    public async Task InitializeAsync(string scanditLicence)

    {

        if (IsInitialized)

            return;

        licence = scanditLicence;

        await InitContextAsync(licence);

        InitBarcodeScanning();

        IsInitialized = true;

    }

    public async Task ChangeMode(ScanditMode newMode)

    {

        if (!IsInitialized)

            throw new InvalidOperationException("Must initialize first!");

        if (CurrentScanditMode == newMode)

            return;

        Reset();

        await InitContextAsync(licence);

        switch (newMode)

        {

            case ScanditMode.BarcodeScanning:

                InitBarcodeScanning();

                break;

            case ScanditMode.MatrixScan:

                InitMatrixScan();

                break;

        }

    }

    private async Task InitContextAsync(string scanditLicence)

    {

        Context = DataCaptureContext.ForLicenseKey(scanditLicence);

        await Context.SetFrameSourceAsync(Camera);

    }

    private void InitBarcodeScanning()

    {

        BarcodeCaptureSettings = BarcodeCaptureSettings.Create();

        BarcodeCaptureSettings.CodeDuplicateFilter = TimeSpan.FromSeconds(4);

        BarcodeCaptureSettings.EnableSymbologies(symbologies);

        BarcodeCapture = BarcodeCapture.Create(Context, BarcodeCaptureSettings);

        BarcodeCapture.Enabled = false; //Starts disabled for performance purposes

        BarcodeCapture.Feedback.Success = new Feedback(null, null); //No feedback for this example

        CurrentScanditMode = ScanditMode.BarcodeScanning;

    }

    private void InitMatrixScan()

    {

        BarcodeTrackingSettings = BarcodeTrackingSettings.Create(BarcodeTrackingScenario.A);

        BarcodeTrackingSettings.EnableSymbologies(symbologies);

        BarcodeTracking = BarcodeTracking.Create(Context, BarcodeTrackingSettings);

        BarcodeTracking.Enabled = false; //Starts disabled for performance purposes

        CurrentScanditMode = ScanditMode.MatrixScan;

    }

    private void Reset()

    {

        BarcodeCapture = null;

        BarcodeCaptureSettings = null;

        BarcodeTracking = null;

        BarcodeTrackingSettings = null;

        Context = null;

    }

}

Create a base ViewModel for MatrixScan

Nothing new if you remember the previous article but this time it is the MatrixScan objects that we propagate instead of the Barcode Scanning. This time we have to implement two interfaces instead of one: IbarcodeTrackingListener and IbarcodeTrackingBasicOverlayListener.

It would look like this:

public abstract class MatrixScanViewModelBase : YourViewModelBase, IBarcodeTrackingListener, IBarcodeTrackingBasicOverlayListener

{

    private static readonly object lockObject = new();

    public Camera Camera => ScanditManager.Instance.Camera;

    public DataCaptureContext Context => ScanditManager.Instance.Context;

    public BarcodeTracking BarcodeTracking => ScanditManager.Instance.BarcodeTracking;

    protected abstract void OnMatrixScanScannedBarcodes(IEnumerable<Barcode> scannedBarcodes);

    protected abstract Brush GetBrushFor(Barcode barcode); //Overrides must be thread safe

    protected void StartCameraScanner()

    {

        BarcodeTracking.AddListener(this);

    }

    protected void StopCameraScanner()

    {

        BarcodeTracking.RemoveListener(this);

    }

    protected async Task<bool> StartCameraScannerReadingAsync()

    {

        PermissionStatus permissionStatus = await Permissions.CheckStatusAsync<Permissions.Camera>();

        bool readingStarted = false;

        if (permissionStatus != PermissionStatus.Granted)

        {

            permissionStatus = await Permissions.RequestAsync<Permissions.Camera>();

            if (permissionStatus == PermissionStatus.Granted)

                readingStarted = await Camera.SwitchToDesiredStateAsync(FrameSourceState.On);

        }

        else

        {

            readingStarted = await Camera.SwitchToDesiredStateAsync(FrameSourceState.On);

        }

        if (readingStarted)

            ScanditManager.Instance.BarcodeTracking.Enabled = true;

        return readingStarted;

    }

    protected async Task<bool> StopCameraScannerReadingAsync()

    {

        ScanditManager.Instance.BarcodeTracking.Enabled = false;

        return await Camera.SwitchToDesiredStateAsync(FrameSourceState.Off);

    }

    #region IBarcodeTrackingListener implementation

    public void OnObservationStarted(BarcodeTracking barcodeTracking) { }

    public void OnObservationStopped(BarcodeTracking barcodeTracking) { }

    public void OnSessionUpdated(

        BarcodeTracking barcodeTracking,

        BarcodeTrackingSession session,

        IFrameData frameData)

    {

        if (session == null || !session.TrackedBarcodes.Any())

            return;

        barcodeTracking.Enabled = false;

        lock (lockObject)

        {

            try

            {

                OnMatrixScanScannedBarcodes(

                    session.TrackedBarcodes.Values.Select(it => it.Barcode)

                );

            }

            finally

            {

                barcodeTracking.Enabled = true;

            }

        }

    }

    #endregion

    #region IBarcodeTrackingBasicOverlayListener implementation

    public Brush BrushForTrackedBarcode(

        BarcodeTrackingBasicOverlay overlay,

        TrackedBarcode trackedBarcode)

    {

        return GetBrushFor(trackedBarcode.Barcode);

    }

    public void OnTrackedBarcodeTapped(

        BarcodeTrackingBasicOverlay overlay,

        TrackedBarcode trackedBarcode)

    {

    }

    #endregion

}

We create a new view whose ViewModel must inherit from the previous base ViewModel. We create your ContentPage and define the following in the xaml:

<scanditCore:DataCaptureView

    DataCaptureContext="{Binding Context}">

 

    <scanditBarcode:BarcodeTrackingBasicOverlay

        BarcodeTracking="{Binding BarcodeTracking}"

        Listener="{Binding .}"/>

</scanditCore:DataCaptureView>

We must not forget to add the reference to scanditCore and scanditBarcode:

xmlns:scanditCore="clr-namespace:Scandit.DataCapture.Core.UI.Unified;assembly=ScanditCaptureCoreUnified"

xmlns:scanditBarcode="clr-namespace:Scandit.DataCapture.Barcode.UI.Unified;assembly=ScanditBarcodeCaptureUnified"

Now we just need to overwrite OnMatrixScanScannedBarcodes and GetBrushFor and call the methods to start/stop the camera and read when we need to. I don't include it here but it's interesting to do this when the app goes to the background or comes back to the foreground. We must not forget to initialize the dependency service with the scandit license before entering the view and changing the mode to Matrix Scan.

Conclusion

We see that just as in the previous article that the Scandit API, however much and very useful its functionality may be, is cumbersome to use. I miss in this API being able to work with interface instances instead of classes, that is, this API does not have dependency inversion.

We are forced to propagate API objects further than should be necessary for more advanced functionality which does not fit or is not the focus of this article. It also forces us to invent a solution through abstract super classes to have a single implementation of the interfaces it needs through the Listener pattern, which is not used by C# programmers due to the availability of events.