Redth / ZXing.Net.Mobile

Barcode Scanner for Xamarin.iOS, Xamarin.Android, UWP and Tizen
MIT License
1.07k stars 701 forks source link

zxing.HasTorch returns false on Android even phone has a torch #942

Open EmilAlipiev opened 3 years ago

EmilAlipiev commented 3 years ago

I tried your sample and code below doesn't display the flash button although my phone has has torch, HasTorch returns always false. If set it true, torch is just working fine on my code. it seems like a bug on android. I tried 3 beta as well. same problem

    overlay = new ZXingDefaultOverlay()
            {
                TopText = "Hold your phone up to the barcode",
                BottomText = "Scanning will happen automatically",
                ShowFlashButton = zxing.HasTorch,
                AutomationId = "zxingDefaultOverlay",
            };
thhowl commented 3 years ago

Torch does have issues on Android. FlashCommand property of DefaultOverlay does not work. IsTorchOn property of ScannerView does not work. First time Torch is turned on, it doesn't turn on... (Note: I also could not get property IsScanning to work properly: see workaround below)

Below is what I had to do to get the Torch button to work. (Version 2.4.1 of the library - I haven't tried 3.0 in Beta)

I recommend just setting ShowFlashButton to true. (Am curious: Is there any device who's camera is capable enough to run this library, yet does not have a torch? Are there known issues with always setting it to true?)

I do MVVM, but couldn't for the Torch because of those properties not working. (I do use MVVM for the scan Result)

XAML:

?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="http://prismlibrary.com"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="YourNameSpace.ScanPage"
             xmlns:zxing="clr-namespace:ZXing.Net.Mobile.Forms;assembly=ZXing.Net.Mobile.Forms"
             >
    <StackLayout>
        <Button Text="Cancel"
                Command="{Binding ExclusiveCommand}" 
                CommandParameter="Cancel"
                TextColor="Black"
                BackgroundColor="LightGray"
                FontSize="10"
                HorizontalOptions="FillAndExpand"
                />
        <Grid VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
            <zxing:ZXingScannerView x:Name="ScannerView"
                                    IsAnalyzing="{Binding IsAnalyzing}"
                                Result="{Binding Result, Mode=OneWayToSource}"
                                ScanResultCommand="{Binding ScanResultCommand}"
                                WidthRequest="200"
                                HeightRequest="200" />
            <!-- DOES NOT WORK in View -->
            <!--IsTorchOn="{Binding IsTorchOn, Mode=TwoWay}"-->

            <zxing:ZXingDefaultOverlay
            x:Name="ScannerOverlay"
            TopText="Place the red line over the barcode."
            BottomText="Hold the device steady for a second."
            ShowFlashButton="True"
            Opacity="0.8"
            FlashButtonClicked="ScannerOverlay_FlashButtonClicked" />
            <!-- DOES NOT WORK in View -->
            <!--FlashCommand="{Binding FlashCommand}"-->

        </Grid>
    </StackLayout>
</ContentPage>

Code Behind for Torch: (and a few other things I couldn't get working with MVVM / bound properties)

using System;
using System.Collections.Generic;
using Xamarin.Forms;
using ZXing.Mobile;

namespace YourNameSpace
{
public partial class ScanPage : ContentPage
{
    public ScanPage()
    {
        InitializeComponent();

        // https://blog.verslu.is/xamarin/xamarin-forms-xamarin/zxing-android-skipping-frames/
        ScannerView.Options.DelayBetweenAnalyzingFrames = 5; // 5 milliseconds, and lower than the default - weird that it's better
        ScannerView.Options.DelayBetweenContinuousScans = 2000; //2000
        ScannerView.Options.InitialDelayBeforeAnalyzingFrames = 300;
        ScannerView.Options.TryHarder = false;
        ScannerView.Options.TryInverted = false;
        ScannerView.Options.AutoRotate = false;
        ScannerView.Options.UseFrontCameraIfAvailable = false;
        ScannerView.Options.PossibleFormats.Clear();
        ScannerView.Options.PossibleFormats.Add(ZXing.BarcodeFormat.CODE_128);
    }

    private bool _isFirstTorchOnRequest = true;

    private void ScannerOverlay_FlashButtonClicked(Button sender, EventArgs e)
    {
        if (_isFirstTorchOnRequest) // else won't turn on with 1st click
        {
            ScannerView.IsTorchOn = true;
            _isFirstTorchOnRequest = false;
        }
        else
        {
            ScannerView.IsTorchOn = !ScannerView.IsTorchOn;
        }
    }

    protected override void OnAppearing()
    {
        base.OnAppearing();

        ScannerView.IsScanning = true;
    }

    protected override void OnDisappearing()
    {
        base.OnDisappearing();

        ScannerView.IsScanning = false;
    }
}
}

Relevant parts of ViewModel:

    private bool _doScan = true;
    private bool _isAnalyzing = true; // start with analysis on

    public bool IsAnalyzing
    {
        get => _isAnalyzing;
        set => SetProperty(ref _isAnalyzing, value);
    }

    private Result _result;
    public Result Result
    {
        get => _result;
        set => SetProperty(ref _result, value);
    }

    private DelegateCommand _scanResultCommand;
    public DelegateCommand ScanResultCommand => _scanResultCommand ??= new DelegateCommand(ExecuteScanResultCommand);//, CanExecuteScanResultCommand);

    /// <summary>
    /// Unlike your typical button command, this methods is called from the hardware, on a non-UI thread
    /// </summary>
    private void ExecuteScanResultCommand()
    {
        if (!_doScan) // use this non-UI property to block re-entry, as this call comes in on a non-UI thread
        {
            return;
        }

        _doScan = false; // doing this immediately, in this thread, to really avoid re-entry

        _player.PlayScannerBeep(); // play early because human response is slow

        _deviceService.BeginInvokeOnMainThread(async () =>
        {
            IsAnalyzing = false; // while could set _doScane false earlier, must call the rest from UI thread
            var barcode = Result.Text;  // not exactly what I do, but same principle.
        });

        // NOTE:  DO NOT set _doScan = true here, because this code runs in separate thread from Main UI thread, BEFORE GoBackWithBarcodeAsync completes.
    }
knocte commented 3 years ago

...I haven't tried 3.0 in Beta)

Hey @thhowl can you try 3.0 beta please?

thhowl commented 3 years ago

@knocte , I will try 3.0 beta next week.

EmilAlipiev commented 3 years ago

I already wrote in the issue that i tried and problem persists.

LymarV commented 3 years ago

I had the same issue and it appeared that I didn't have the Android FLASHLIGHT permission in the AndroidManifest.xml (silly me).