Morse
Morse

Reputation: 9144

Android CameraPreview not rendering for MLKit barcode analyis on MAUI custom control

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

Screenshot

Edit:

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

Answers (1)

Morse
Morse

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

Related Questions