Scandit. Xamarin Forms integration of the SDK for camera data capture
Scandit is an SDK for capturing barcodes and/or text from the device's camera. It is available for Android, iOS, Windows, Web, Cordova, Xamarin (Android, iOS and Forms), React Native, Flutter and other hybrid SDKs.
It recognises practically all existing barcode symbologies, both single-dimensional (EAN, Code128, UPCE...) and two-dimensional (QR, Pdf417, DataMatrix...).
It offers the ability to detect multiple barcodes in a single frame with its Matrix Scan mode. The latter also allows us to draw overlays on the camera interface, i.e. augmented reality.
What do we need for camera data capture?
- Visual Studio (Obvious)
- Destination API 21 (5.0 Lollipop) or higher in our Android project
- Destination iOS 11.0 or higher in our iOS project
- A Scandit license
Note that these licences vary according to the functionality required so that for a given licence some features may be "capped".
Adding the nuget packages
They should be added to both shared and native projects:
- Scandit.DataCapture.Core.Xamarin.Forms
- Scandit.DataCapture.Barcode.Xamarin.Forms
Create a dependency service
Depending on whether your project uses an IoC engine, or uses DepedencyService or neither, the best implementation may be one or the other and may or may not require a dependency investment. Whatever the case, we need a service to internally manage the objects needed to work with Scandit. For simplicity, we will use the singleton pattern in this example as we need a single instance.
public sealed class ScanditManager {
private static readonly Lazy<ScanditManager> lazyInstance = new(
() => new ScanditManager(),
LazyThreadSafetyMode.PublicationOnly
);
public static ScanditManager Instance => lazyInstance.Value;
private ScanditManager() {}
}
Now we must decide if we want Scandit to use Barcode Scanning (Sequential code detection), Matrix Scan (Detection of n barcodes per frame), Matrix Scan + AR or all at once. In this article we only deal with Barcode Scanning, the most basic, otherwise it would be too long.
Implementing barcode scanning
The steps to follow are:
- Add a DataCaptureContext property.
- Add a Camera property.
- Add a CameraSettings property.
- Add a BarcodeCapture property.
- Add a BarcodeCaptureSettings property.
- Define the supported symbologies (The less, the better the performance).
- Initialise Scandit.
It would look like this:
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 readonly ISet<Symbology> symbologies = new HashSet<Symbology>
{
Symbology.Code128,
Symbology.Gs1Databar,
Symbology.Qr
};
public bool IsInitialized { get; private set; } = false;
public DataCaptureContext Context { get; private set; }
public Camera Camera { get; private set; } = Camera.GetCamera(CameraPosition.WorldFacing);
public CameraSettings CameraSettings { get; } = BarcodeCapture.RecommendedCameraSettings;
public BarcodeCapture BarcodeCapture { get; private set; }
public BarcodeCaptureSettings BarcodeCaptureSettings { get; private set; }
//Must be initialized before navigating a page with scandit dependency
public async Task InitializeAsync(string scanditLicence)
{
Context = DataCaptureContext.ForLicenseKey(scanditLicence);
await Context.SetFrameSourceAsync(Camera);
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.
IsInitialized = true;
}
}
Now let's create a base ViewModel to inherit from when we need a view that uses Scandit. We need this ViewModel to implement IbarcodeCaptureListener.
public class ScanditViewModelBase : YourViewModelBase, IBarcodeCaptureListener
{
private static readonly Brush brush = new Brush(Color.Transparent, Color.White, 2);
public Camera Camera => ScanditManager.Instance.Camera;
public DataCaptureContext Context => ScanditManager.Instance.Context;
public BarcodeCapture BarcodeCapture => ScanditManager.Instance.BarcodeCapture;
protected virtual Brush Brush => brush;
protected void StartCameraScanner()
{
BarcodeCapture.AddListener(this);
}
protected void StopCameraScanner()
{
BarcodeCapture.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.BarcodeCapture.Enabled = true;
return readingStarted;
}
public async Task<bool> StopCameraScannerReadingAsync()
{
ScanditManager.Instance.BarcodeCapture.Enabled = false;
return await Camera.SwitchToDesiredStateAsync(FrameSourceState.Off);
}
protected void OnScanditBarcodeScanned(Barcode scannedBarcode) { }
#region IBarcodeCaptureListener implementation
public void OnObservationStarted(BarcodeCapture barcodeCapture) { }
public void OnObservationStopped(BarcodeCapture barcodeCapture) { }
public void OnSessionUpdated(
BarcodeCapture barcodeCapture,
BarcodeCaptureSession session,
IFrameData frameData) {}
public void OnBarcodeScanned(
BarcodeCapture barcodeCapture,
BarcodeCaptureSession session,
IFrameData frameData)
{
if (session == null || !session.NewlyRecognizedBarcodes.Any())
return;
barcodeCapture.Enabled = false;
Barcode scannedBarcode = session.NewlyRecognizedBarcodes.First();
try
{
OnScanditBarcodeScanned(scannedBarcode);
}
finally
{
barcodeCapture.Enabled = true;
}
}
#endregion
}
We create a new view whose ViewModel must inherit from the previous base ViewModel. We create its ContentPage and define the following in the xaml:
<scanditCore:DataCaptureView
DataCaptureContext="{Binding Context}" >
<scanditBarcode:BarcodeCaptureOverlay
BarcodeCapture="{Binding BarcodeCapture}"
Brush="{Binding Brush}"/>
</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"
We just need to overwrite OnScanditBarcodeScanned and invoke the methods to start/stop the camera and reading when we need to. I don't include it here but it is interesting to do this when the app goes to the background or comes back to the foreground. Don't forget to initialise the dependency service with the scandit license before entering the view.
Scandit offers great functionalities that are very useful for customers involved in logistics or who, for whatever reason, scan barcodes in their business logic.
However, it can be observed that code detection is not as efficient as soon as the conditions are not perfect (lighting, shadows, etc.). For example, it is sufficient to print a barcode on paper and compare its detection with its equivalent on a monitor.
Matrix Scan, which we have not had time to see here, is the great value of the product. For example, in a typical package download operation, Matrix Scan will detect all the codes at once and display real-time feedback, even with augmented reality. Without this tool the user would have to laser scan each downloaded package sequentially.
As C# programmers the use of the listener pattern is rare as we have events but Scandit intends to improve this in future versions.