Reputation: 9144
I have a QR code which needs to read through Google's ML kit for Android since Zxing.MAUI doesnt read it. I followed the code in ml-kit/vision/barcode-scanning/android and VinayByte/mlkit-qr-code-scan-android-kotlin and also hjam40/Camera.MAUI I was not able to use Camera.MAUI since it doesnt work for .net 9
Here is my custom control code, if anyone has an idea why the preview is not showing up that would be a great help!
builder.ConfigureMauiHandlers(handlers =>
{
handlers.AddHandler<QRCodeReaderView, QRCodeReaderViewHandler>();
});
Custom control
public class QRCodeReaderView : View {}
public partial class QRCodeReaderViewHandler
{
public static IPropertyMapper<QRCodeReaderView, QRCodeReaderViewHandler> PropertyMapper = new PropertyMapper<QRCodeReaderView, QRCodeReaderViewHandler>(ViewMapper)
{
};
public static CommandMapper<QRCodeReaderView, QRCodeReaderViewHandler> CommandMapper = new CommandMapper<QRCodeReaderView, QRCodeReaderViewHandler>(ViewCommandMapper)
{
};
public QRCodeReaderViewHandler() : base(PropertyMapper, CommandMapper) { }
}
using System.Diagnostics;
using AndroidX.Camera.Core;
using AndroidX.Camera.Core.ResolutionSelector;
using AndroidX.Camera.Lifecycle;
using AndroidX.Camera.View;
using AndroidX.Core.Content;
using AndroidX.Lifecycle;
using Java.Lang;
using Java.Util.Concurrent;
using Microsoft.Maui.Handlers;
using Exception = System.Exception;
using Size = Android.Util.Size;
namespace QRCodeScanner;
public partial class QRCodeReaderViewHandler : ViewHandler<QRCodeReaderView, AndroidQRCodeReaderView>
{
private Preview? _cameraPreview;
private PreviewView _previewView;
private IExecutorService? cameraExecutor;
private ICamera? _camera;
protected override AndroidQRCodeReaderView CreatePlatformView()
{
var context = MauiContext?.Context ?? Platform.AppContext;
_previewView = new PreviewView(context);
return new AndroidQRCodeReaderView(context, _previewView);
}
protected override void ConnectHandler(AndroidQRCodeReaderView platformView)
{
base.ConnectHandler(platformView);
if (MauiContext is not null)
{
Connect(MauiContext, platformView.PreviewView);
}
}
public void Connect(IMauiContext mauiContext, PreviewView previewView)
{
try
{
if (mauiContext.Context is null)
{
return;
}
cameraExecutor = Executors.NewSingleThreadExecutor();
var cameraProviderFuture = ProcessCameraProvider.GetInstance(mauiContext.Context);
if (cameraExecutor is null)
return;
var cameraProvider = cameraProviderFuture.Get() as ProcessCameraProvider;
if (cameraProvider is null)
return;
cameraProviderFuture.AddListener(new Runnable(() =>
{
var resolutionSelector = new ResolutionSelector.Builder().SetResolutionStrategy(new ResolutionStrategy(boundSize: new Size(1280, 700),
fallbackRule: ResolutionStrategy.FallbackRuleClosestHigherThenLower))
.Build();
_cameraPreview = new Preview.Builder().SetResolutionSelector(resolutionSelector).Build();
_cameraPreview.SetSurfaceProvider(cameraExecutor, previewView.SurfaceProvider);
var imageAnalyzer = new ImageAnalysis.Builder()
.SetBackpressureStrategy(ImageAnalysis.StrategyKeepOnlyLatest)
.SetResolutionSelector(resolutionSelector)
.Build();
var mLkitQrCodeAnalyzer = new MLkitQRCodeAnalyzer();
imageAnalyzer.SetAnalyzer(cameraExecutor, mLkitQrCodeAnalyzer);
cameraProvider.UnbindAll();
var cameraSelector = CameraSelector.DefaultBackCamera;
var hasCamera = cameraProvider.HasCamera(cameraSelector);
Debug.WriteLine($"hasCamera: {hasCamera}");
if (previewView.Context is ILifecycleOwner lifecycleOwner)
{
_camera = cameraProvider.BindToLifecycle(lifecycleOwner, cameraSelector, _cameraPreview, imageAnalyzer);
}
else if (Platform.CurrentActivity is ILifecycleOwner maLifecycleOwner)
{
_camera = cameraProvider.BindToLifecycle(maLifecycleOwner, cameraSelector, _cameraPreview, imageAnalyzer);
}
}),
ContextCompat.GetMainExecutor(mauiContext.Context));
}
catch (Exception ex)
{
Debug.WriteLine(ex.StackTrace);
}
}
}
Camera permissions
<uses-permission android:name="android.permission.CAMERA"/>
private async Task CheckAndRequestCameraPermission()
{
var status = await Permissions.CheckStatusAsync<Permissions.Camera>();
if (status != PermissionStatus.Granted)
{
status = await Permissions.RequestAsync<Permissions.Camera>();
if (status != PermissionStatus.Granted)
{
await DisplayAlert("Camera Permission Denied", "This app needs access to the camera to scan QR codes.", "OK");
}
}
}
protected override async void OnAppearing()
{
base.OnAppearing();
await CheckAndRequestCameraPermission();
}
UI
<?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:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:local="clr-namespace:QRCodeScanner"
x:DataType="{x:Type local:MainViewModel}"
x:Class="QRCodeScanner.MainPage">
<ContentPage.Resources>
<toolkit:BoolToObjectConverter x:TypeArguments="x:String"
x:Key="BoolToStringConverter"
TrueObject="On"
FalseObject="Off" />
</ContentPage.Resources>
<VerticalStackLayout Spacing="10"
Padding="20">
<Label Text="Below is the scanner"
HorizontalOptions="Center" />
<local:QRCodeReaderView HorizontalOptions="Fill"
VerticalOptions="Fill"
HeightRequest="400"
WidthRequest="400"
ResultCommand="{Binding QRResultCommand}"
IsFlashOn="{Binding IsFlashOn}"
IsVisible="{Binding IsVisible}"
x:Name="qrCodeReaderView" />
<HorizontalStackLayout HorizontalOptions="Center"
Spacing="20"
IsVisible="{Binding IsVisible}">
<Label Text="{Binding IsFlashOn, Source={Reference qrCodeReaderView}, Converter={StaticResource BoolToStringConverter}, StringFormat='Flash {0}'}"
VerticalOptions="Center"
VerticalTextAlignment="Center" />
<Switch IsToggled="{Binding IsFlashOn}" />
</HorizontalStackLayout>
<Label HorizontalOptions="Center"
VerticalOptions="End"
Margin="0,0,0,20"
Text="{Binding Result}" />
</VerticalStackLayout>
</ContentPage>
Screenshot
On further analysis through Logcat I found the issue to be as following
Unable to configure camera Camera@461d7c2[id=0]
java.util.concurrent.TimeoutException: Future[androidx.camera.core.impl.utils.futures.ListFuture@4c0973e] is not done within 5000 ms.
The possible solution to set android:hardwareAccelerated="true"
application level or MainActivity level is not working too.
Upvotes: 0
Views: 71
Reputation: 9144
Answering my own question in case anyone else stumbles upon this.
Changing native platform implementation from Android.Views.View
to AndroidX.CoordinatorLayout.Widget.CoordinatorLayout
like following fixed the issue of preview not showing.
protected override AndroidQRCodeReaderView CreatePlatformView()
{
var context = MauiContext?.Context ?? Platform.AppContext;
_previewView = new PreviewView(context)
{
LayoutParameters = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent)
};
var _relativeLayout = new RelativeLayout(context)
{
LayoutParameters = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent)
};
_relativeLayout.AddView(_previewView);
var platformview = new AndroidQRCodeReaderView(context);
platformview.AddView(_relativeLayout);
return platformview;
}
Reference: github/afriscic/BarcodeScanning.Native.Maui
Upvotes: 1