0

I am making a barcode scanning functionality. The camera feed is setting _currentImage.

// CameraView.dart

  CameraImage _currentImage;
  CameraImage takePhoto() => _currentImage;

  void imageStream(CameraImage image) async {
    print("_currentImage is set with " + image.toString()); // keeps saying it sets 'currentImage'
    this._currentImage = image;
  }

Later on I call takePhoto from button pressed handler and call 'setState' empty...

  void pressedShowBarcodeOnScreenButton() async {
    var image = this.cameraView.takePhoto();
    if (image == null) {
      print(// occurs after having called empty 'setState' every time button is pressed
          "image was null, maybe it is not possible to take images this fast!");
      return;
    }

    var barcode =
        await VisionService.getInstance() // is not producing any errors
            .analyzeBarcode(ImageUtils.toAbstractImage(image));

    if (Utils.isNullOrEmpty(barcode)) {
      return;
    }

    print("got barcode: " + barcode); // prints all barcodes ok, but stops doing so when calling 'setState'

    setState(() {
      // Makes it so that 'currentImage' is null at all times, despite continuously set by imageStream given from log
      //this.scannedBarcodes = [...this.scannedBarcodes, barcode]; // my intention eventual to update a list
    });
  }

I suspect it might be due to some underlaying dart behavour, or there is some sort of bug? Why does the setState call prevent me from accessing the _currentImage - or why is blocked from being set by the imageStream()?

nvoigt
  • 75,013
  • 26
  • 93
  • 142
lolelo
  • 698
  • 6
  • 18

1 Answers1

0

The CameraView which contained the CameraController that had the stream, was part of the page to display the barcodes. So SetState(){} triggered a reconstruction of the CameraView, and the controller was not able to be reconstructed. Probably the old stream was producing the log and was misleading.

It helped to extract the CameraController in this scenario, and have it accessed as singleton, inside CameraViewController that I made:

//...
    static CameraController _cameraController;
    static Future<CameraController> getCameraControllerInstance() async {
    if (_cameraController == null) {
      var cameras = await availableCameras();
      if (cameras?.length == 0) {
        print("the device did not have a camera");
        return null;
      }
      _cameraController = CameraController(cameras[0], ResolutionPreset.max);
      await _cameraController.initialize();
      _cameraController.startImageStream(updateCurrentImage);
    }

    return _cameraController;
  }

And I use it in a FutureBuilder like so, in the CameraView:

FutureBuilder futureBuilder() => FutureBuilder<CameraController>(
      future: CameraViewController.getCameraControllerInstance(), // <--!!!
      builder:
          (BuildContext context, AsyncSnapshot<CameraController> snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return LoadingPage();
        }

        return content(snapshot.data);
      });

I am tempted to have one instance of the CameraView around at all times (?), since it looks glitchy when updating the list - but as I see it would be anti pattern as the Widget's are supposed to rerender (immutable) themselves:

The content of the page that has the barcode list:

Widget content() {
    return Container(
        child: (Column(children: [
      cameraView, // camera view part of page and recontructed on 'scannedProducts' state change
      header(),
      scanButton(),
      Column(
        children: this.scannedProducts(), /// list
      )
    ])));
  }
lolelo
  • 698
  • 6
  • 18