Granting permission using camera package pauses the debugger on an exception

1,561

May be the actual culprit is didChangeAppLifecycleState.

Once you call await _controller.initialize(); and the permission dialog is shown, the lifecycle event AppLifecycleState.inactive is triggered and current controller is disposed as per your code in didChangeAppLifecycleState, hence when the application resumes after permissions given and tries to continue, it throws error.

Try removing

if (state == AppLifecycleState.inactive) {
  _controller?.dispose();
}

Or have a local variable to check if initializing and ignore dispose when initiliazing like

Future<void> _initialize() async {
  await _getCameras();
  _controller = CameraController(_availableCameras[0], ResolutionPreset.high);
  _initializing = true;
  await _controller.initialize();
  _initializing = false;
  if (!mounted) {
    return;
  }
  setState(() {});
}

and in didChangeAppLifecycleState

if (state == AppLifecycleState.inactive && !_initializing) {
  _controller?.dispose();
}

EDIT:

May be, I think I found the issue, the actual issue is didChangeAppLifecycleState as expected, the if clause in the didChangeAppLifecycleState, if it truns out to be true, _controller is being disposed, if not _setCurrentCamera is just disposing any active controller. Hence when you invoke initialize and wait for permissions, before permission future resolves, the _controller is being disposed by didChangeAppLifecycleState.

My solution would work with simple change. Change your initState to

@override
void initState() {
  super.initState();
  _initializing = true; // set to true
  WidgetsBinding.instance.addObserver(this);
  _initialize();
}

change your _initialize function to make _initializing = false after intializing like,

Future<void> _initialize() async {
  await _getCameras();
  _controller = CameraController(_availableCameras[0],ResolutionPreset.high);
  await _controller.initialize();
  _initializing = false; // set to false
  if (!mounted) {
    return;
  }
  setState(() {});
}

and your didChangeAppLifecycleState to

@override
void didChangeAppLifecycleState(AppLifecycleState state) {
  if(_initializing){
    return;
  }
  if (state == AppLifecycleState.inactive) {
    _controller?.dispose();
  } else if (state == AppLifecycleState.resumed) {
    if (_controller != null) {
      _setCurrentCamera(_controller.description);
    }
  }
}

This way, if _initializing == true you never dispose the current controller.


Hope that helps!

Share:
1,561
boomboxboy
Author by

boomboxboy

Updated on December 12, 2022

Comments

  • boomboxboy
    boomboxboy over 1 year

    I am using the camera package for a simple functionality. I am mostly following the example the package provided. When I open the camera widget page, the package automatically prompts to provide permission to the camera and microphone. After clicking allow to both the permissions the debugger is paused with an exception:

    Exception has occurred.
    FlutterError (A CameraController was used after being disposed.
    Once you have called dispose() on a CameraController, it can no longer be used.).
    

    Here is the required code :

    class CameraPage extends StatefulWidget {
      @override
      _CameraPageState createState() => _CameraPageState();
    }
    
    class _CameraPageState extends State<CameraPage>
        with WidgetsBindingObserver {
      CameraController _controller;
      List<CameraDescription> _availableCameras;
      ...
    
      @override
      void initState() {
        super.initState();
        WidgetsBinding.instance.addObserver(this);
        _initialize();
      }
    
      Future<void> _initialize() async {
        await _getCameras();
        _controller = CameraController(_availableCameras[0], ResolutionPreset.high);
        await _controller.initialize();
        if (!mounted) {
          return;
        }
        setState(() {});
      }
    
      @override
      void didChangeAppLifecycleState(AppLifecycleState state) {
        if (state == AppLifecycleState.inactive) {
          _controller?.dispose();
        } else if (state == AppLifecycleState.resumed) {
          if (_controller != null) {
            _setCurrentCamera(_controller.description);
          }
        }
      }
    
      @override
      void dispose() {
        WidgetsBinding.instance.removeObserver(this);
        _controller.dispose();
        super.dispose();
      }
    
    
      Future<List<CameraDescription>> _getCameras() async {
        List<CameraDescription> camDescriptions;
          camDescriptions = await availableCameras();
    
          _availableCameras = camDescriptions;
        return camDescriptions;
      }
    
      @override
      Widget build(BuildContext context) {
        ...
      }
    
    
      Future<void> _setCurrentCamera(CameraDescription cameraDescription) async {
        if (_controller != null) {
          await _controller.dispose();
        }
        _controller = CameraController(
          cameraDescription,
          ResolutionPreset.high,
          enableAudio: false,
        );
    
        // If the _controller is updated then update the UI.
        _controller.addListener(() {
          if (mounted) setState(() {});
    
          if (_controller.value.hasError) {
            print('Camera error ${_controller.value.errorDescription}');
          }
        });
    
        try {
          await _controller.initialize();
        } on CameraException catch (e) {
          _showCameraException(e);
        }
    
        if (mounted) {
          setState(() {});
        }
      }
    
      void _switchCamera() {
        if (_controller != null && !_controller.value.isRecordingVideo) {
          CameraLensDirection direction = _controller.description.lensDirection;
          CameraLensDirection required = direction == CameraLensDirection.front
              ? CameraLensDirection.back
              : CameraLensDirection.front;
          for (CameraDescription cameraDescription in _availableCameras) {
            if (cameraDescription.lensDirection == required) {
              _setCurrentCamera(cameraDescription);
              return;
            }
          }
        }
      }
    
    
      void _showCameraException(CameraException e) {
        String errorText = 'Error: ${e.code}\nError Message: ${e.description}';
        print(errorText);
      }
    
    }
    

    The debugger is pointing the exception here:

      Future<void> _initialize() async {
        await _getCameras();
        _controller = CameraController(_availableCameras[0], ResolutionPreset.high);
        //-------------HERE------------------
        await _controller.initialize();
        if (!mounted) {
          return;
        }
        setState(() {});
      }
    

    Once I resume the debugger and try opening this camera page again there is no error/exception anymore. It Is happening only after accepting permissions for the first time.