Scandit. Integración en Xamarin Forms del SDK para la captura de datos desde cámara
Scandit es un SDK para capturar códigos de barras y/o texto desde la cámara del dispositivo. Está disponible para Android, iOS, Windows, Web, Cordova, Xamarin (Android, iOS y Forms), React Native, Flutter y demás SDK’s híbridos.
Reconoce prácticamente todas las simbologías de códigos de barras existentes, tanto monodimensionales (EAN, Code128, UPCE...), como bidimensionales (QR, Pdf417, DataMatrix...).
Ofrece la capacidad de detectar múltiples códigos de barras en un sólo frame con su modo Matrix Scan. Este último también nos permite dibujar superposiciones sobre la interfaz de la cámara, es decir, realidad aumentada.
¿Qué necesitamos para la captura de datos desde la cámara?
- Visual Studio (Obvio).
- Destino API 21 (5.0 Lollipop) o superior en nuestro proyecto Android.
- Destino iOS 11.0 o superior en nuestro proyecto iOS.
- Una licencia de Scandit.
Nótese que estas licencias varían según las funcionalidades requeridas por lo que para una licencia dada alguna característica podría estar “capada”.
Añadiendo los paquetes nuget
Deben añadirse tanto al proyecto compartido como a los proyectos nativos:
- Scandit.DataCapture.Core.Xamarin.Forms
- Scandit.DataCapture.Barcode.Xamarin.Forms
Crea un servicio de dependencia
Dependiendo de si tu proyecto usa un motor IoC, o usa DepedencyService o ninguno de los dos, la mejor implementación puede ser una u otra y puede requerir una inversión de dependencia o no. Sea cual sea el caso, necesitamos un servicio para gestionar internamente los objetos necesarios para trabajar con Scandit. Para simplificar, usaremos el patrón singleton en este ejemplo pues necesitamos una instancia única.
public sealed class ScanditManager {
private static readonly Lazy<ScanditManager> lazyInstance = new(
() => new ScanditManager(),
LazyThreadSafetyMode.PublicationOnly
);
public static ScanditManager Instance => lazyInstance.Value;
private ScanditManager() {}
}
Ahora debemos decidir si queremos Scandit para usar Barcode Scanning (Detección secuencial de códigos), Matrix Scan (Detección de n códigos de barras por cada frame), Matrix Scan + AR o todo a la vez. En este artículo sólo tratamos Barcode Scanning, el más básico, pues de lo contrario se haría muy extenso.
Implementando Barcode Scanning
Los pasos a seguir son:
- Añadir una propiedad DataCaptureContext.
- Añadir una propiedad Camera.
- Añadir una propiedad CameraSettings.
- Añadir una propiedad BarcodeCapture.
- Añadir una propiedad BarcodeCaptureSettings.
- Definir las simbologías soportadas (Cuanto menos, mejor rendimiento).
- Inicializar Scandit.
Nos quedaría algo tal que así:
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;
}
}
Ahora vamos a crear un ViewModel base del que heredar cuando necesitemos una vista que use Scandit. Necesitamos que este ViewModel implemente 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
}
Creamos una nueva vista cuyo ViewModel debe heredar del ViewModel base anterior. Creamos su ContentPage y definimos lo siguiente en el xaml:
<scanditCore:DataCaptureView
DataCaptureContext="{Binding Context}" >
<scanditBarcode:BarcodeCaptureOverlay
BarcodeCapture="{Binding BarcodeCapture}"
Brush="{Binding Brush}"/>
</scanditCore:DataCaptureView>
No debemos olvidar añadir la referencia a scanditCore y scanditBarcode:
xmlns:scanditCore="clr-namespace:Scandit.DataCapture.Core.UI.Unified;assembly=ScanditCaptureCoreUnified"
xmlns:scanditBarcode="clr-namespace:Scandit.DataCapture.Barcode.UI.Unified;assembly=ScanditBarcodeCaptureUnified"
Ya sólo nos hace falta sobre escribir OnScanditBarcodeScanned e invocar los métodos para iniciar/detener la cámara y la lectura cuando necesitemos. No lo incluyo aquí pero es interesante hacer esto cuando la app pasa a segundo plano o vuelve a primer plano. No debemos olvidar inicializar el servicio de dependencia con la licencia de scandit antes de entrar en la vista.
Scandit ofrece grandes funcionalidades muy útiles para clientes dedicados a la logística o que, por el motivo que fuera, escanean códigos de barras en su lógica de negocio.
Sin embargo se observa que la detección de códigos no es tan eficiente en cuanto las condiciones no son perfectas (Iluminación, sombras, etc). Sin ir más lejos basta con imprimir en papel un código de barras y comparar su detección con su equivalente en un monitor.
Matrix Scan, que no hemos tenido tiempo de verlo aquí, es el gran valor del producto. Por ejemplo en una típica operación de descarga de paquetes, Matrix Scan detectará todos los códigos a la vez y mostrará feedback en tiempo real, incluso con realidad aumentada. Sin esta herramienta el usuario tendría que escanear con láser cada paquete descargado secuencialmente.
Como programadores C# el uso del patrón listener se hace raro pues disponemos de eventos pero Scandit pretende mejorar esto en futuras versiones.