0

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

  • Each user page has a generated QR code that holds a part of the endpoint address
  • There are 2 types of users, Trainer and Trainee
  • There is an endpoint POST method to links 2 users that finally should look like this: /users/links/{initiatorType}/{initiatorId}/{targetCode}/link so for example the if a Trainer is logged in, his/her QR code would be: /users/links/Trainer/1
  • Imagine both Trainer and Trainee meet at the gym, the Trainer shows his/her QR code to the Trainee in order to scan it, another service method completes the endpoint address and sends a POST request. Obviously I also have other actions like Share code, Copy code to clipboard so it can be sent to another user via a Messenger or any other communicator.

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.

1 Answer 1

0

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 !

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.