Friday, 1 August 2025

Barcode/QR Code Scanning in MAUI Apps

 

Implementing barcode and QR code scanning in a .NET MAUI application is straightforward with the right libraries. Here are the main approaches:

Option 1: Using ZXing.Net.MAUI

The most popular solution is the ZXing.Net.MAUI library:

  1. Install the package:

    bash
    dotnet add package ZXing.Net.MAUI
  2. Initialize in MauiProgram.cs:

    csharp
    builder.UseMauiApp<App>()
           .UseBarcodeReader(); // Add this line
  3. Add camera permission:

    • For Android, add to AndroidManifest.xml:

      xml
      <uses-permission android:name="android.permission.CAMERA" />
    • For iOS, add to Info.plist:

      xml
      <key>NSCameraUsageDescription</key>
      <string>This app needs camera access to scan barcodes</string>
  4. Create a scanning page:

    xaml
    <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:zxing="clr-namespace:ZXing.Net.MAUI.Controls;assembly=ZXing.Net.MAUI"
                 x:Class="YourApp.ScanPage">
        <zxing:CameraBarcodeReaderView
            x:Name="barcodeReader"
            BarcodesDetected="BarcodesDetected" />
    </ContentPage>
  5. Handle the scan results:

    csharp
    private void BarcodesDetected(object sender, BarcodeDetectionEventArgs e)
    {
        foreach (var barcode in e.Results)
        {
            Console.WriteLine($"Barcode: {barcode.Format} -> {barcode.Value}");
            // Process the scanned value
        }
    }

Option 2: Using Google's ML Kit (Android/iOS)

For more advanced scanning capabilities:

  1. Install Google ML Kit for MAUI

  2. Follow the platform-specific setup instructions

  3. Implement scanning similar to ZXing but with ML Kit's enhanced features

Option 3: Device Camera + Custom Processing

For complete control:

  1. Use MAUI's MediaPicker to take photos

  2. Process images with a library like ZXing.Net:

    csharp
    var photo = await MediaPicker.CapturePhotoAsync();
    using var stream = await photo.OpenReadAsync();
    var reader = new BarcodeReader();
    var result = reader.Decode(stream);

Tips for Better Scanning

  • Ensure proper camera permissions are requested at runtime

  • Add a viewfinder overlay to guide users

  • Consider adding vibration on successful scan

  • Handle low-light conditions with torch/toggle flash option

  • Test with various barcode types (QR, UPC, EAN, etc.)



Adding a Viewfinder Overlay for Barcode Scanning in MAUI

To improve the user experience when scanning barcodes/QR codes, you can add a viewfinder overlay that helps users align the code properly. Here's how to implement it with ZXing.Net.MAUI:

Option 1: Simple Rectangle Overlay (XAML)

xaml
<Grid>
    <!-- Camera Preview -->
    <zxing:CameraBarcodeReaderView
        x:Name="barcodeReader"
        BarcodesDetected="BarcodesDetected" />
    
    <!-- Viewfinder Overlay -->
    <Grid>
        <!-- Semi-transparent surrounding area -->
        <BoxView Color="Black" Opacity="0.6"/>
        
        <!-- Transparent center area with border -->
        <Frame 
            HorizontalOptions="Center"
            VerticalOptions="Center"
            WidthRequest="250"
            HeightRequest="250"
            CornerRadius="10"
            BackgroundColor="Transparent"
            BorderColor="White"
            Padding="0">
            
            <!-- Corner marks (optional) -->
            <Grid>
                <!-- Top-left corner -->
                <BoxView Color="White" WidthRequest="30" HeightRequest="5" HorizontalOptions="Start" VerticalOptions="Start"/>
                <BoxView Color="White" WidthRequest="5" HeightRequest="30" HorizontalOptions="Start" VerticalOptions="Start"/>
                
                <!-- Top-right corner -->
                <BoxView Color="White" WidthRequest="30" HeightRequest="5" HorizontalOptions="End" VerticalOptions="Start"/>
                <BoxView Color="White" WidthRequest="5" HeightRequest="30" HorizontalOptions="End" VerticalOptions="Start"/>
                
                <!-- Bottom-left corner -->
                <BoxView Color="White" WidthRequest="30" HeightRequest="5" HorizontalOptions="Start" VerticalOptions="End"/>
                <BoxView Color="White" WidthRequest="5" HeightRequest="30" HorizontalOptions="Start" VerticalOptions="End"/>
                
                <!-- Bottom-right corner -->
                <BoxView Color="White" WidthRequest="30" HeightRequest="5" HorizontalOptions="End" VerticalOptions="End"/>
                <BoxView Color="White" WidthRequest="5" HeightRequest="30" HorizontalOptions="End" VerticalOptions="End"/>
            </Grid>
        </Frame>
        
        <!-- Instruction text -->
        <Label Text="Align barcode within the frame"
               TextColor="White"
               FontSize="16"
               HorizontalOptions="Center"
               VerticalOptions="End"
               Margin="0,0,0,40"/>
    </Grid>
</Grid>

Option 2: Animated Viewfinder (Code-Behind)

For a more dynamic effect, add this to your page's code-behind:

csharp
// Add to your page class
private BoxView _scanLine;
private bool _isAnimating;

protected override void OnAppearing()
{
    base.OnAppearing();
    StartScanLineAnimation();
}

protected override void OnDisappearing()
{
    base.OnDisappearing();
    _isAnimating = false;
}

private void StartScanLineAnimation()
{
    _scanLine = new BoxView
    {
        Color = Colors.Red,
        WidthRequest = 250,
        HeightRequest = 2,
        HorizontalOptions = LayoutOptions.Center,
        VerticalOptions = LayoutOptions.Center,
        TranslationY = -125 // Start at top of viewfinder
    };
    
    if (this.FindByName<Grid>("overlayGrid") is Grid overlayGrid)
    {
        overlayGrid.Children.Add(_scanLine);
    }
    
    _isAnimating = true;
    Device.StartTimer(TimeSpan.FromMilliseconds(16), () =>
    {
        if (!_isAnimating) return false;
        
        _scanLine.TranslationY += 2;
        if (_scanLine.TranslationY >= 125)
        {
            _scanLine.TranslationY = -125;
        }
        return true;
    });
}

Option 3: Custom GraphicsView (Advanced)

For complete control over the drawing:

xaml
<GraphicsView x:Name="viewfinderOverlay" 
              Drawable="{StaticResource ViewfinderDrawable}"
              HorizontalOptions="Fill"
              VerticalOptions="Fill"/>

Create a custom drawable class:

csharp
public class ViewfinderDrawable : IDrawable
{
    public void Draw(ICanvas canvas, RectF dirtyRect)
    {
        // Draw semi-transparent overlay
        canvas.FillColor = Colors.Black.WithAlpha(0.6f);
        canvas.FillRectangle(dirtyRect);
        
        // Clear center area
        var centerWidth = 250;
        var centerHeight = 250;
        var centerX = (dirtyRect.Width - centerWidth) / 2;
        var centerY = (dirtyRect.Height - centerHeight) / 2;
        
        canvas.BlendMode = BlendMode.Clear;
        canvas.FillRectangle(centerX, centerY, centerWidth, centerHeight);
        canvas.BlendMode = BlendMode.Normal;
        
        // Draw border
        canvas.StrokeColor = Colors.White;
        canvas.StrokeSize = 3;
        canvas.DrawRectangle(centerX, centerY, centerWidth, centerHeight);
        
        // Draw corner marks
        var cornerSize = 20;
        var lineThickness = 5;
        
        // Top-left
        canvas.DrawLine(centerX, centerY, centerX + cornerSize, centerY);
        canvas.DrawLine(centerX, centerY, centerX, centerY + cornerSize);
        
        // Top-right
        canvas.DrawLine(centerX + centerWidth - cornerSize, centerY, 
                        centerX + centerWidth, centerY);
        canvas.DrawLine(centerX + centerWidth, centerY, 
                        centerX + centerWidth, centerY + cornerSize);
        
        // Bottom-left
        canvas.DrawLine(centerX, centerY + centerHeight - cornerSize, 
                        centerX, centerY + centerHeight);
        canvas.DrawLine(centerX, centerY + centerHeight, 
                        centerX + cornerSize, centerY + centerHeight);
        
        // Bottom-right
        canvas.DrawLine(centerX + centerWidth - cornerSize, centerY + centerHeight, 
                        centerX + centerWidth, centerY + centerHeight);
        canvas.DrawLine(centerX + centerWidth, centerY + centerHeight - cornerSize, 
                        centerX + centerWidth, centerY + centerHeight);
    }
}

Additional Enhancements

  1. Torch/Toggle Flash Button:

    xaml
    <Button Text="Toggle Flash" 
            Clicked="ToggleFlashClicked"
            HorizontalOptions="End"
            VerticalOptions="Start"
            Margin="20"/>
    csharp
    private void ToggleFlashClicked(object sender, EventArgs e)
    {
        barcodeReader.IsTorchOn = !barcodeReader.IsTorchOn;
    }
  2. Vibration on Successful Scan:

    csharp
    private async void BarcodesDetected(object sender, BarcodeDetectionEventArgs e)
    {
        if (e.Results.Any())
        {
            try 
            {
                // Vibrate for 200ms
                await Vibration.Default.Vibrate(TimeSpan.FromMilliseconds(200));
            }
            catch { }
            
            // Process result...
        }
    }


Exception Handling in .NET MAUI with Device Information Logging

To handle exceptions in a MAUI app and log device information to your server, you'll need to implement a comprehensive error handling strategy. Here's how to do it:

1. Global Exception Handling

First, set up a global exception handler in your MAUI app:

csharp
// In MauiProgram.cs
public static MauiApp CreateMauiApp()
{
    var builder = MauiApp.CreateBuilder();
    
    // ... other configurations
    
    // Add global exception handling
    AppDomain.CurrentDomain.UnhandledException += (sender, args) => 
    {
        HandleException(args.ExceptionObject as Exception);
    };
    
    return builder.Build();
}

2. Platform-Specific Device Information

Create a service to collect device information:

csharp
public interface IDeviceInfoService
{
    string GetDeviceName();
    string GetDeviceId();
    string GetCountry();
    Task<(double latitude, double longitude)> GetLocationAsync();
}

// Platform implementations (Android, iOS, etc.)
public partial class DeviceInfoService : IDeviceInfoService
{
    public partial string GetDeviceName();
    public partial string GetDeviceId();
    public partial string GetCountry();
    public partial Task<(double latitude, double longitude)> GetLocationAsync();
}

3. Exception Handling Service

Create a service to handle exceptions and send them to your server:

csharp
public class ExceptionHandlerService
{
    private readonly IDeviceInfoService _deviceInfoService;
    private readonly HttpClient _httpClient;
    
    public ExceptionHandlerService(IDeviceInfoService deviceInfoService)
    {
        _deviceInfoService = deviceInfoService;
        _httpClient = new HttpClient();
    }
    
    public async Task HandleExceptionAsync(Exception ex)
    {
        try
        {
            var deviceName = _deviceInfoService.GetDeviceName();
            var deviceId = _deviceInfoService.GetDeviceId();
            var country = _deviceInfoService.GetCountry();
            var location = await _deviceInfoService.GetLocationAsync();
            var currentDateTime = DateTime.UtcNow.ToString("o");
            
            var errorData = new ErrorLog
            {
                DeviceName = deviceName,
                DeviceId = deviceId,
                DateTime = currentDateTime,
                Country = country,
                Latitude = location.latitude,
                Longitude = location.longitude,
                ExceptionType = ex.GetType().Name,
                Message = ex.Message,
                StackTrace = ex.StackTrace,
                InnerException = ex.InnerException?.ToString()
            };
            
            await SendToServerAsync(errorData);
        }
        catch (Exception loggingEx)
        {
            // Fallback logging if sending to server fails
            Console.WriteLine($"Failed to log exception: {loggingEx}");
        }
    }
    
    private async Task SendToServerAsync(ErrorLog errorData)
    {
        var json = JsonSerializer.Serialize(errorData);
        var content = new StringContent(json, Encoding.UTF8, "application/json");
        
        // Replace with your server endpoint
        var response = await _httpClient.PostAsync("https://your-server.com/api/errorlogs", content);
        
        if (!response.IsSuccessStatusCode)
        {
            throw new HttpRequestException($"Failed to send error log: {response.StatusCode}");
        }
    }
}

public class ErrorLog
{
    public string DeviceName { get; set; }
    public string DeviceId { get; set; }
    public string DateTime { get; set; }
    public string Country { get; set; }
    public double Latitude { get; set; }
    public double Longitude { get; set; }
    public string ExceptionType { get; set; }
    public string Message { get; set; }
    public string StackTrace { get; set; }
    public string InnerException { get; set; }
}

4. Platform-Specific Implementations

Android Implementation:

csharp
public partial class DeviceInfoService
{
    public partial string GetDeviceName()
    {
        return $"{DeviceInfo.Manufacturer} {DeviceInfo.Model}";
    }
    
    public partial string GetDeviceId()
    {
        return Android.Provider.Settings.Secure.GetString(
            Android.App.Application.Context.ContentResolver,
            Android.Provider.Settings.Secure.AndroidId);
    }
    
    public partial string GetCountry()
    {
        var locale = Java.Util.Locale.Default;
        return locale.Country;
    }
    
    public partial async Task<(double latitude, double longitude)> GetLocationAsync()
    {
        try
        {
            var request = new GeolocationRequest(GeolocationAccuracy.Medium);
            var location = await Geolocation.GetLocationAsync(request);
            return (location?.Latitude ?? 0, location?.Longitude ?? 0);
        }
        catch
        {
            return (0, 0);
        }
    }
}

iOS Implementation:

csharp
public partial class DeviceInfoService
{
    public partial string GetDeviceName()
    {
        return DeviceInfo.Name;
    }
    
    public partial string GetDeviceId()
    {
        return UIKit.UIDevice.CurrentDevice.IdentifierForVendor?.ToString();
    }
    
    public partial string GetCountry()
    {
        return Foundation.NSLocale.CurrentLocale.CountryCode;
    }
    
    public partial async Task<(double latitude, double longitude)> GetLocationAsync()
    {
        try
        {
            var request = new GeolocationRequest(GeolocationAccuracy.Medium);
            var location = await Geolocation.GetLocationAsync(request);
            return (location?.Latitude ?? 0, location?.Longitude ?? 0);
        }
        catch
        {
            return (0, 0);
        }
    }
}

5. Register Services in MAUI

csharp
// In MauiProgram.cs
builder.Services.AddSingleton<IDeviceInfoService, DeviceInfoService>();
builder.Services.AddSingleton<ExceptionHandlerService>();

6. Using the Exception Handler

In your code, you can now handle exceptions:

csharp
try
{
    // Your code that might throw exceptions
}
catch (Exception ex)
{
    var exceptionHandler = MauiApplication.Current.Services.GetService<ExceptionHandlerService>();
    await exceptionHandler.HandleExceptionAsync(ex);
    
    // Optionally show user-friendly message
    await Shell.Current.DisplayAlert("Error", "An unexpected error occurred", "OK");
}

7. Server-Side Implementation

On your server, you'll need an endpoint to receive these error logs. Here's a basic ASP.NET Core example:

csharp
[ApiController]
[Route("api/errorlogs")]
public class ErrorLogController : ControllerBase
{
    [HttpPost]
    public IActionResult LogError([FromBody] ErrorLog errorLog)
    {
        // Store in database or logging system
        Console.WriteLine($"Received error: {JsonSerializer.Serialize(errorLog)}");
        
        return Ok();
    }
}

Important Considerations:

  1. Permissions: Ensure you have required permissions for location (in AndroidManifest.xml and Info.plist)

  2. Privacy: Inform users you're collecting this data (may require privacy policy updates)

  3. Performance: Consider batching errors if the app is offline

  4. Security: Use HTTPS and consider authentication for your error logging endpoint

  5. Offline Support: Implement local storage for errors when offline with sync later

This implementation provides a robust error handling system that captures all the requested device information and sends it to your server for analysis.

Barcode/QR Code Scanning in MAUI Apps

  Implementing barcode and QR code scanning in a .NET MAUI application is straightforward with the right libraries. Here are the main approa...

Ads2