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 !