Search code examples
fluttercamera

FutureBuilder and Camera


I'm new to Flutter and I still struggle with some Future aspects. I have a bottom navigation bar, and switching between pages is done with a PageView. The problem is that when I switch to my "photo" page, it shows a Circular Progress Indicator FOREVER. I am a bit lost, honestly I have no idea what I'm doing wrong, it looks like the future builder never gets the value. Just to give more info: if I do an hot refresh, the camera starts to work and the picture appears.

This is the PhotoScreen page:

import 'dart:async';
import 'dart:io';

import 'package:camera/camera.dart';
import 'package:flutter/material.dart';

// A screen that allows users to take a picture using a given camera.
class PhotoScreen extends StatefulWidget {
  const PhotoScreen({
    Key key,
  }) : super(key: key);

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

class PhotoScreenState extends State<PhotoScreen> {
  CameraDescription camera;
  CameraController _controller;
  Future<void> _initializeControllerFuture;

  @override
  void initState() {
    super.initState();
    // Obtain a list of the available cameras on the device.
    inizializza();
  }

  @override
  void dispose() {
    // Dispose of the controller when the widget is disposed.
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Take a picture')),
      // Wait until the controller is initialized before displaying the
      // camera preview. Use a FutureBuilder to display a loading spinner
      // until the controller has finished initializing.
      body: FutureBuilder<void>(
        future: _initializeControllerFuture,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            // If the Future is complete, display the preview.
            return CameraPreview(_controller);
          } else {
            // Otherwise, display a loading indicator.
            return Center(child: CircularProgressIndicator());
          }
        },
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.camera_alt),
        // Provide an onPressed callback.
        onPressed: () async {
          // Take the Picture in a try / catch block. If anything goes wrong,
          // catch the error.
          try {
            // Ensure that the camera is initialized.
            await _initializeControllerFuture;

            // Attempt to take a picture and get the file `image`
            // where it was saved.
            final image = await _controller.takePicture();
          } catch (e) {
            // If an error occurs, log the error to the console.
            print(e);
          }
        },
      ),
    );
  }

  void inizializza() async {
    final cameras = await availableCameras();

    // Get a specific camera from the list of available cameras.
    final firstCamera = cameras.first;
    // To display the current output from the Camera,
    // create a CameraController.
    _controller = CameraController(
      // Get a specific camera from the list of available cameras.
      firstCamera,
      // Define the resolution to use.
      ResolutionPreset.medium,
    );

    // Next, initialize the controller. This returns a Future.
    _initializeControllerFuture = _controller.initialize();
  }
}

Solution

  • The inizializza method is actually asynchronous and you are executing it in the initState(), which is not. I think your inizializza method should be like this:

    Future<void> inizializza() async {
        final cameras = await availableCameras();
    
        // Get a specific camera from the list of available cameras.
        final firstCamera = cameras.first;
        // To display the current output from the Camera,
        // create a CameraController.
        _controller = CameraController(
          // Get a specific camera from the list of available cameras.
          firstCamera,
          // Define the resolution to use.
          ResolutionPreset.medium,
        );
    
        // Next, initialize the controller. This returns a Future.
        await _controller.initialize();
      }
    

    And that is the Future to be received by the FutureBuilder, instead of executing it in the initState()

    FutureBuilder<void>(
      future: inizializza(),
      ...
    )