Sławomir Majchrzak
Sławomir Majchrzak

Reputation: 1

Decoding QrCodes with Camera in Maui Blazor Hybrid

I need help with my .NET Maui Blazor Hybrid project. What I am trying to do is the following:

I only do C# in Maui Blazor via blazorWebView, no native code.

I am unable to detect and retrieve the QR's string. Can anyone help me with a simple solution to this problem? Many thanks for any hints/directions.

OK, so I have the nugets : ZXing.Net.Maui.Controls

I added the BarcodeReader in MauiProgram.cs

        builder.UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
            }).UseBarcodeReader();

The camera permissions in both Android and Ios added.

This is my razor page:

@page "/UserProfile"

<CustomButtonComponent OnClick="ScanQrCode">
 Scan
</CustomButtonComponent>
        
<div">@_qrCodeResult</div>

<ErrorMessageComponent @ref="_errorMessage"/>

@code {
    private string? _loggedUserId;
    private User? _user;
    private ErrorMessageComponent? _errorMessage;
    private string? _url;
    private string? _qrCodeResult = "Not Scanned";

    private async Task ScanQrCode()
    {
        try
        {
            // Check permissions for camera
            var status = await Permissions.CheckStatusAsync<Permissions.Camera>();
            if (status != PermissionStatus.Granted)
            {
                status = await Permissions.RequestAsync<Permissions.Camera>();
                if (status != PermissionStatus.Granted)
                {
                    Logger.LogError("Camera permission denied.");
                    return;
                }
            }

            // Use the MediaPicker for photo capture
            var photo = await MediaPicker.CapturePhotoAsync();
            if (photo == null)
            {
                Logger.LogError("No photo taken.");
                return;
            }

            await using var stream = await photo.OpenReadAsync();
            var result = await DecodeQrCodeFromStream(stream);

            if (result != null)
            {
                _qrCodeResult = result.Text;
                Logger.LogInformation($"QR Code result: {_qrCodeResult}");

                // Handle the scanned QR code
                // await SendLinkRequest(_qrCodeResult);
            }
            else
            {
                _qrCodeResult = "No QR code found.";
                Logger.LogWarning("No QR code found.");
            }
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "Error scanning QR code: {Message}", ex.Message);
            _qrCodeResult = "Error scanning QR Code";
        }
    }
    
    // Method to decode QR code from stream
    private async Task<Result?> DecodeQrCodeFromStream(Stream stream)
    {
        try
        {
            // Convert the stream to a byte[] (this is necessary for creating the LuminanceSource)
            var imageBytes = await GetImageBytesFromStream(stream);

            // Create the LuminanceSource from the image bytes
            var luminanceSource = new RGBLuminanceSource(imageBytes, 2000, 2000);

            // Decode the QR code using BarcodeReader
            var barcodeReader = new BarcodeReaderGeneric();
            return barcodeReader.Decode(luminanceSource);
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "Error decoding QR code: {Message}", ex.Message);
            return null;
        }
    }

    // Helper method to convert Stream to byte[]
    private async Task<byte[]> GetImageBytesFromStream(Stream stream)
    {
        using var ms = new MemoryStream();
        await stream.CopyToAsync(ms);
        return ms.ToArray();
    }
    
    private async Task SendLinkRequest(string scannedUrl)
    {
        ...
    }

}

So far I get No QR code found error.

Upvotes: 0

Views: 53

Answers (1)

Sławomir Majchrzak
Sławomir Majchrzak

Reputation: 1

OK guys, I got it sorted making a mix of Gerald’s: https://www.youtube.com/watch?v=2dllz4NZC0I

and Pavlos’s solutions: https://pavlodatsiuk.hashnode.dev/implementing-maui-blazor-with-zxing-qr-barcode-scanner

Created a CameraPage.xaml

<?xml version="1.0" encoding="utf-8”?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="FitnessPal.CameraPage"
             xmlns:zxing="clr-namespace:ZXing.Net.Maui.Controls;assembly=ZXing.Net.MAUI.Controls">

    <ContentPage.Content>
        <Grid>
            <!-- Dark background to simulate modal effect -->
            <BoxView Color="Black" Opacity="0.9" />

            <!-- Scanner View in a smaller container -->
            <Frame Padding="1" CornerRadius="10" BackgroundColor="DodgerBlue" Opacity="0.9" WidthRequest="350"
                   HeightRequest="500"
                   HorizontalOptions="Fill" VerticalOptions="Fill" BorderColor="SkyBlue">
                <Grid>
                    <zxing:CameraBarcodeReaderView
                        x:Name="CameraBarcodeScannerView"
                        IsDetecting="True"
                        BarcodesDetected="BarcodesDetected"
                        VerticalOptions="Fill"
                        HorizontalOptions="Fill" />

                    <!-- Close Button -->
                    <Button Text="Close" TextColor="DarkOrange" FontSize="24" Clicked="OnCloseClicked"
                            HorizontalOptions="Center"
                            VerticalOptions="End"
                            Margin="5" />
                </Grid>
            </Frame>
        </Grid>
    </ContentPage.Content>
</ContentPage>

Then added necessary code behind to the CameraPage.xaml.cs

using System.Diagnostics;
using ZXing.Net.Maui;
namespace FitnessPal;

public partial class CameraPage
{
    public CameraPage()
    {
        InitializeComponent();

        CameraBarcodeScannerView.Options = new BarcodeReaderOptions
        {
            Formats = BarcodeFormats.TwoDimensional,
            AutoRotate = true,
            Multiple = false
        };
    }

    private TaskCompletionSource<BarcodeResult> _scanTask = new();

    public Task<BarcodeResult> WaitForResultAsync()
    {
        _scanTask = new TaskCompletionSource<BarcodeResult>();

        if (CameraBarcodeScannerView != null)
        {
            CameraBarcodeScannerView.IsDetecting = true;
        }

        Debug.WriteLine($"Status: {_scanTask.Task.Status}");
        return _scanTask.Task;
    }

    private async void BarcodesDetected(object? sender, BarcodeDetectionEventArgs eventArgs)
    {
        try
        {
            if (_scanTask.Task.IsCompleted) return;

            CameraBarcodeScannerView.IsDetecting = false;
            _scanTask.TrySetResult(eventArgs.Results[0]); 
            Debug.WriteLine("Scan result: " + eventArgs.Results[0].Value);

            await MainThread.InvokeOnMainThreadAsync(async () =>
            {
                if (Navigation?.ModalStack.Count > 0)
                {
                    await Navigation.PopModalAsync();
                    Debug.WriteLine("Closed CameraPage modal.");
                }
            });
        }
        catch (Exception e)
        {
            Debug.WriteLine($"Error detecting barcode: {e.Message}");
        }
    }

    private async void OnCloseClicked(object? sender, EventArgs eventArgs)
    {
        try
        {
            CameraBarcodeScannerView.IsDetecting = false;

            if (Application.Current?.MainPage?.Navigation != null)
            {
                await Application.Current.MainPage.Navigation.PopModalAsync();
                Debug.WriteLine("Camera Page Closed.");
            }
            else
            {
                Debug.WriteLine("Go to Main Page.");
            }
        }
        catch (Exception e)
        {
            Debug.WriteLine($"Error closing modal: {e.Message}");
        }
    }
}

Then to be totally clear here is the rest of important changes: MainPage.xaml

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:FitnessPal"
             x:Class="FitnessPal.MainPage"
             BackgroundColor="{DynamicResource PageBackgroundColor}">
    <ContentPage.Content>
        <Grid>
            <!-- Add padding to respect the safe area -->
            <Grid.Padding>
                <OnPlatform x:TypeArguments="Thickness">
                    <On Platform="iOS" Value="0" />
                    <On Platform="Android" Value="0" />
                </OnPlatform>
            </Grid.Padding>
        <BlazorWebView x:Name="BlazorWebView" HostPage="wwwroot/index.html">
            <BlazorWebView.RootComponents>
                <RootComponent Selector="#app" ComponentType="{x:Type local:Components.Routes}"/>
            </BlazorWebView.RootComponents>
        </BlazorWebView>
        </Grid>
    </ContentPage.Content>
</ContentPage>

MainPage.xaml.cs - getting rid of the top navigation

public MainPage()
    {
        InitializeComponent();
        NavigationPage.SetHasNavigationBar(this, false);
    }

App.xaml.cs

public partial class App
{
    public App()
    {
        InitializeComponent();
    }
    
    protected override Window CreateWindow(IActivationState? activationState)
    {
        return new Window(new NavigationPage(new MainPage())) {Title="Fitness Pal"};
    }
}

Obviously the MauiProgram.cs needs the UseBarcodeReader:

builder.UseMauiApp<App>()
    .ConfigureFonts(fonts =>
    {
        fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
    }).UseBarcodeReader();

Permissions added to Android manifest and ios plist.

But the last and not least, use of the CameraPage in the Blazor razor page:

    <CustomButtonComponent CssClass="btn-outline-fp-info btn-fp-normal" OnClick="ScanQrCodeAsync">
        Scan
    </CustomButtonComponent>

    <div>@_qrCodeResult</div>
</div>

@code {
...
    private string _qrCodeResult = "Not scanned";

    private async Task ScanQrCodeAsync()
    {
        var scanResult = await GetScanResultsAsync();
        Debug.WriteLine($"Scan result: {scanResult.Format} -> {scanResult.Value}");
        
        _qrCodeResult = $"Type: {scanResult.Format} -> {scanResult.Value}";
    }

    private async Task<BarcodeResult> GetScanResultsAsync()
    {
        var cameraPage = new CameraPage();
        await Application.Current?.MainPage?.Navigation.PushModalAsync(cameraPage)!;

        return await cameraPage.WaitForResultAsync();
    }

I did not remove the Debug lines but hey, you know what it was for ;)

For anyone interested, there you go. Now I can use it in my ‘target’ nethod that retrieves the QR code value, appends the rest to make it a valid endpoint adress and finally send the POST request to that API endpoint. Happy Coding !

Upvotes: 0

Related Questions