Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Permission error not reset in start/stop method #1247

Open
Tracked by #1225
eshaanagarwal opened this issue Nov 26, 2024 · 10 comments
Open
Tracked by #1225

Permission error not reset in start/stop method #1247

eshaanagarwal opened this issue Nov 26, 2024 · 10 comments
Assignees

Comments

@eshaanagarwal
Copy link

Hi ! I am developing a usecase in a Flutter APP of qr scanner via your great package. After being denied twice (permanently denied), i use openSettings() function to open settings.

Incase i configure the camera permission, i need that my app when the lifecycle state -> resumed to start the Camera instead of displaying the ScannerErrorWidget.
I use Cubit to maintain Permission state across App to enable dialog boxes and rebuild app as and when the state changes.

This is some of the code i have written for it:

@override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (!controller.value.isInitialized) {
      return;
    }

    switch (state) {
      case AppLifecycleState.detached:
      case AppLifecycleState.hidden:
      case AppLifecycleState.paused:
        return;
      case AppLifecycleState.resumed:
        if (_isReturningFromSettings) {
          _isReturningFromSettings = false;
          context.read<MobileScannerCubit>().checkCameraPermission(); // Recheck permission
        } 
          _initializeCamera();

        break;
      case AppLifecycleState.inactive:
        _isScannerDeactivated = true;
        unawaited(_subscription?.cancel());
        _subscription = null;
        unawaited(controller.stop());
    ```
    
    ```
    void _initializeCamera() {
    if (_isScannerDeactivated) return;
    
    try {
      if (!controller.value.isRunning) {
        _subscription = controller.barcodes.listen(_handleBarcode);
        unawaited(controller.start());
      }
    } catch (e) {
      logger!.d("Error initializing camera: $e");
      _deactivateCamera();
    }
  }

I maintain two states understand the phase of the flow we are at a particular instance

  bool _isScannerDeactivated = false;
bool _isReturningFromSettings = false;

Cubit main functions :

Future<void> checkCameraPermission() async {
  if (Platform.isAndroid || Platform.isIOS) {
      var permissionStatus = await Permission.camera.status;
      logger?.d(permissionStatus);
      if (await Permission.camera.isPermanentlyDenied) {
          emit(MobileScannerPermanentlyDeniedState());
      } else if(await Permission.camera.isDenied){
          emit(MobileScannerDeniedState());
      } else if(await Permission.camera.isGranted){
          emit(MobileScannerGrantedState());
      }
  }
}

Future<void> openAppPermissionSettings() async{
  openAppSettings();
}

Bloc Listner Code

listener: (context, permissionState) {
        if (!_isDialogShowing && !_isScannerDeactivated &&  
        (permissionState is MobileScannerDeniedState || 
            permissionState is MobileScannerPermanentlyDeniedState)) {
          _isDialogShowing = true;  // Set flag before showing dialog
          _showScannerPermissionDialog(context, permissionState);
        } else if (permissionState is MobileScannerGrantedState) {
            _isScannerDeactivated = false;
            _initializeCamera();
        }
      },
      ```
      
      
      ```
child: MobileScanner(
          controller: controller,
          onDetect: _handleBarcode,
          errorBuilder: (scannerContext, error, child) {
            String errorMessage;
            const Widget defaultError = ColoredBox(
                color: Colors.black,
                child: Center(child: Icon(Icons.error, color: Colors.white)),
              );
            if (!_isDialogShowing && !_isScannerDeactivated) {
              if(error.errorCode == MobileScannerErrorCode.permissionDenied){
                errorMessage = errorCameraPermissionDenied;
                context.read<MobileScannerCubit>().checkCameraPermission();
              } else {
                errorMessage = error.toString();
              }
            }
            return defaultError;
          },

I extensively searched across your documentation and examples but couldnt find any examples to hint in this direction.

If you could let me know, any ideas or or direction.

One of the major issues, i am facing is this even after configuring Camera Permission from settings, the Controller Error Widget goes on an inifinite loop of diaplying the error even when i have verified via permission_handler package that the permission status is granted

@navaronbracke
Copy link
Collaborator

navaronbracke commented Nov 26, 2024

In didChangeAppLifecycleState you should check if the app has permission to use the camera, and if not, return from the method.

On Android a permission request triggers a lifecycle change when the permission popup opens. When the permission is denied, the popup closes, which triggers didChangeAppLifecycleState. If you do not check the permission state in that method, the usual resume handling will happen again, which would trigger the permission dialog again.
If the user permanently denied the permission, then the app would no longer show the permission dialog, but didChangeAppLifecycleState will still be called again, which results in an infinite loop.

This is the reason why I updated the guidance in 1ab7f5a

See also the example that includes lifecycle handling: https://github.com/juliansteenbakker/mobile_scanner/blob/master/example/lib/barcode_scanner_controller.dart#L62-L66

@navaronbracke
Copy link
Collaborator

navaronbracke commented Nov 26, 2024

So specifically in your case

@override
 void didChangeAppLifecycleState(AppLifecycleState state) {
    if (!controller.value.isInitialized) {
      return;
    }

is not sufficient. Either you check the permission state from permission_handler or check if the controller.value.error is the permission denied error code, and return if either of those indicates that the permission is not granted

@eshaanagarwal
Copy link
Author

Thank you so much for your help. This idea seems to be worth checking. I was checking permission but at a later stage which i now understand is the issue. The version we are using for your product which doesnt have the hasCameraPermission and therefore overlooked the guidance. Nevertheless i will use the error check inside.

@eshaanagarwal
Copy link
Author

eshaanagarwal commented Nov 26, 2024

I had one question : (This might sound silly) but after you got to settings from openSettings(), and enable the permission - using controller.value.error did not work in my case as the the error is still there in the controller although using permission handler - i have cross checked that the permission handler that its is granted. Therefore the controller doesnt restart with the screen again.

I have verified that at later stage we emit permissionGranted state but still the controller has controller.value.error has Permission denied error and is not null.

So how does this work ?

@navaronbracke
Copy link
Collaborator

In that case you should stop the scanner before restarting it. The mobile_scanner plugin does not talk to the permission handler plugin to figure out what the permission state is.

However, while double checking that, I see that we do not set the error to null in the stop method's copyWith() call:
https://github.com/juliansteenbakker/mobile_scanner/blob/master/lib/src/mobile_scanner_controller.dart#L354-L359

We also do not reset the error to null when the start method succeeded:
https://github.com/juliansteenbakker/mobile_scanner/blob/master/lib/src/mobile_scanner_controller.dart#L297-L306

Would you mind opening a PR to fix that? Otherwise I'll look into this myself.

@navaronbracke navaronbracke self-assigned this Nov 26, 2024
@navaronbracke navaronbracke changed the title Help Needed : Scanner Controller Reinitialization after Camera Permission enabled via openSettings() Permission error not reset in start/stop method Nov 26, 2024
@eshaanagarwal
Copy link
Author

eshaanagarwal commented Nov 26, 2024

Yeah, I have been trying to stop and start exactly like you said.

Thanks ! This might fix it.

I would love to work on this. I will try to put up a PR by tomorrow after work, If you would like. Otherwise, thanks for offering.

@navaronbracke
Copy link
Collaborator

I wrote a quick fix, feel free to test it. I'll do that tomorrow as well. Once it is tested, I'll include this in the next beta.

@eshaanagarwal
Copy link
Author

Thanks for the fix, looks good code wise.

I will try to test this today.

@erecord
Copy link

erecord commented Dec 2, 2024

Edit: Sorry I see that #1037 and #773 have been raised about this. I've read through the threads and seems like it's unresolved. Thanks for your help on those threads. I've tried the errorBuilder and error handling but it doesn't get called when in this state.


Hey guys, slightly unrelated to this issue but it's similar. I'm encountering a 'development only' issue whereby if the camera is active and I hot-restart then the camera stays on and the MobileScanner widget does not load up correctly (it just stays on the placeholder builder).

I was wondering if there may be a similar fix for the issue as this fix here. Could we somehow reset the camera when calling start()? I've tried your branch @navaronbracke but it doesn't seem to fix this issue.

The only workaround for my 'development only issue' is to call controller.stop() before doing the hot restart. But it would be nice if there was a check when calling 'start()' to see if it's still active and then reset it. Or somehow to call controller.stop() when a hot-restart occurs (but seems impossible because hot-restart just kills thread or similar).

Sorry if this is a completely different issue - let me know if I should raise a separate issue about this.

Thanks for any thoughts/advice on this.

Debugging on Real Device: iPhone 14, iOS 18.1.1
Package Version: 6.0.2

@navaronbracke
Copy link
Collaborator

@erecord Your issue is indeed related to hot restart, and you are correct that those issues you mentioned already talk about that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants