Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to wait for camera to initialize?

I kept getting an error from the camera.dart that "name" was being called on a null object.

After some time, I realized that the problem was the that the build method is called before the async code in my initstate finished (I'm actually slightly proud that I understood the problem at least :))

I tried many different ways to initialize my camera properly, but I could not. This is the last iteration of my code.

What's the idiomatic way of handling this future?

  class _PicturePreviewState extends State<PicturePreview> {
  List<CameraDescription> cameras;
  CameraDescription camera;
  CameraController cameraController;
  Future<void> initializeController;

  Future<void> getCameras() async {
    try {
      cameras = await availableCameras();     
    } catch(e) {print(e);}
    camera = cameras.last;
    print(camera);
  }

  @override
  void initState() {
    super.initState();
    // getCameras();
    availableCameras().then((availableCameras) {
      cameras = availableCameras;
      camera = cameras.first;
      cameraController = CameraController(
      camera,
      ResolutionPreset.low,
    );
    initializeController = cameraController.initialize();
    print(cameraController.value.isInitialized);
    });
    
    // cameraController = CameraController(
    //   camera,
    //   ResolutionPreset.low,
    // );
    // initializeController = cameraController.initialize();
    // print(cameraController.value.isInitialized);
  }

  @override
  void dispose() {
    cameraController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder<void>(
        future: initializeController,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            // If the Future is complete, display the preview.
              return CameraPreview(cameraController);
          }
          else {
            // Otherwise, display a loading indicator.
            print(snapshot.connectionState);
            return Center(child: CircularProgressIndicator());
          }
        },
      ),

I have been relying on this page to use the camera package, but I could not use it verbatim because I can't keep passing down the camera object down my widget tree.

like image 858
MAA Avatar asked Aug 30 '25 17:08

MAA


2 Answers

I fixed it. I put the initializing of the camera object in the in the parent of the widget.

class _TakeReceiptPictureState extends State<TakeReceiptPicture> {
  List<CameraDescription> cameras;
  CameraDescription camera;

  @override
  void initState() { 
    super.initState();
    availableCameras().then((availableCameras) {
      cameras = availableCameras;
      camera = cameras.first;
    });       
  }

Then made the widget that takes the picture have a parameter of type CameraDescription.

class PicturePreview extends StatefulWidget {
  final CameraDescription camera;
  const PicturePreview(this.camera, {Key key}) : super(key: key);

  @override
  _PicturePreviewState createState() => _PicturePreviewState();
}

Then passed the camera initialized in the parent to picture widget

        onTap: () {
          Navigator.of(context).push(
             PageTransition(
                  type: PageTransitionType.transferRight,
                  child: PicturePreview(camera)),
            );
        }),

by the time the child widget's build method runs, the camera object is already initialized and ready to go.

Now the state of the child have only two variables, the camera controller and the initialize controller future.

  CameraController cameraController;
  Future<void> initializeController;

  @override
  void initState() {
    super.initState();    
    cameraController = CameraController(
      widget.camera,
      ResolutionPreset.low,
    );
    initializeController = cameraController.initialize();
  }

TLDR: let the initialization of the camera object be the responsibility of the parent of the widget.

like image 87
MAA Avatar answered Sep 02 '25 09:09

MAA


A bit late to answer but this can be solved by using a boolean variable to check whether the camera has been initialized or not.

First, define a boolean variable to track the status of the camera.

bool _isCameraInitialized = false;

Next, in the function where you initialize the camera, change the value of the boolean variable to true inside a setState() statement.

await _controller.initialize();
if (_controller.value.isInitialized) {
    setState(() {
    _isCameraInitialized= true;
    })
}

Here, notice that the status of the controller is directly inferred from the controller itself using _controller.value.isInitialized.

Now, inside your build method, build your UI if the camera is initialized, else build a SizedBox().

If the camera is not initialized then the build() function will produce a SizedBox and display it till the camera gets initialized and setState() is called.

Scaffold(
        backgroundColor: Colors.white,
        body: _isCameraInitialized
            ? //your UI here
            : SizedBox(),
)

Do not that this is a very rudimentary approach to solving this problem. To build an app with good UX, you should consider including a loading screen of some sort using the boolean variable.

like image 42
kris Avatar answered Sep 02 '25 10:09

kris