Flutter: Camera Basics

Ken Baldauf

Senior Software Engineer @ Originate

Packages

  • Enables creation of modular, easily shareable code
  • Published to pub.dev
  • Pulled into project via pubspec.yaml
  • Dart Package
    • Consists entirely of Dart code
    • May or may not rely on the Flutter framework
  • Plugin Package
    • API written in Dart + platform-specific implementation for Android and/or iOS

Camera Plugin

  • Developed & maintained by the Flutter team
  • Still under development w/ planned refactor
  • minSdkVersion 21
  • Leverages Camera2 API
  • Supports both still images & video recording
  • Planned Refactor
    • Considering migrating to Camera API to support 16+

Limitations

  • Locked into fixed resolution presets
    • If chosen resolution isn't supported, a lower quality preset will be automatically used
    • max - highest resolution available
    • ultraHigh - 2160p (3840x2160)
    • veryHigh - 1080p (1920x1080)
    • high - 720p (1280x720)
    • medium - 480p (720x480 on Android & 640x480 on iOS)
    • low - 240p (320x240 on Android) & 352x288 on iOS
  • Support for zoom, focus, lighting & other advanced options are still missing

Basic Steps

  • Get available cameras
  • Initialize CameraController
  • Display camera feed with CameraPreview
  • Take Picture with CameraController
  • Display captured photo with Image widget

Get Available Cameras

Future<void> main() async {
  // Ensure that plugin services are initialized so that
  // `availableCameras()` can be called before `runApp().
  WidgetsFlutterBinding.ensureInitialized();
  
  // Obtain a list of the available cameras on the device.
  List<CameraDescription> cameras = await availableCameras();

  // Get a specific camera from the list of available cameras.
  final firstCamera = cameras.first;

  runApp(
    MaterialApp(
      theme: ThemeData.dark(),
      home: CameraApp(
        // Pass the appropriate camera to the CameraApp widget.
        camera: firstCamera,
      ),
    ),
  );
}

Initialize CameraController

class CameraApp extends StatefulWidget {

  final CameraDescription camera;

  const CameraApp({
    Key key,
    @required this.camera,
  }) : super(key: key);

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

Initialize CameraController

class _CameraAppState extends State<CameraApp> {
  CameraController controller;

  @override
  void initState() {
    super.initState();
    controller = CameraController(widget.camera, 
    	ResolutionPreset.medium);
    controller.initialize().then((_) {
      // do nothing if state has already been disposed
      if (!mounted) {
        return;
      }
      setState(() {});
    });
  }

  @override
  void dispose() {
    controller?.dispose();
    super.dispose();
  }
}

Display Camera Feed

  @override
  Widget build(BuildContext context) {
    if (!controller.value.isInitialized) {
      return Container();
    }
    return Scaffold(
      backgroundColor: Theme.of(context).backgroundColor,
      extendBody: true,
      bottomNavigationBar: _buildBottomNavigationBar(),
      body: _buildCameraPreview(),
    );
  }

Display Camera Feed

  Widget _buildCameraPreview() {
    // get screen aspect ratio
    final size = MediaQuery.of(context).size;
    return Container(
      child: Transform.scale(
        scale: controller.value.aspectRatio / size.aspectRatio,
        child: Center(
          child: AspectRatio(
            aspectRatio: controller.value.aspectRatio,
            child: CameraPreview(controller),
          ),
        ),
      ),
    );
  }

Bottom Nav Bar

  Widget _buildBottomNavigationBar() {
    return Container(
      color: Theme.of(context).bottomAppBarColor,
      height: 100.0,
      width: double.infinity,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: <Widget>[
          CircleAvatar(
            backgroundColor: Colors.white,
            radius: 28.0,
            child: IconButton(
              icon: Icon(
                Icons.camera_alt,
                size: 28.0,
                color: Colors.black,
              ),
              onPressed: () async {
                _captureImage();
              }
            ),
          ),
        ],
      ),
    );
  }

Capture Photo

  void _captureImage() async {
    if (controller.value.isInitialized) {
      // getExternalStorageDirectory() comes from the path_provider plugin 
      // which exposes commonly used filesystem locations
      final Directory extDir = await getExternalStorageDirectory();
      final String dirPath = '${extDir.path}/media';
      await Directory(dirPath).create(recursive: true);
      final String filePath = '$dirPath/${_timestamp()}.jpeg';
      print("filePath: $filePath");
      await controller.takePicture(filePath);
      setState(() {});
      Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => DisplayPictureScreen(imagePath: filePath),
        ),
      );
    }
  }
  
  String _timestamp() => DateTime.now().millisecondsSinceEpoch.toString();
  

Display Photo from File

class DisplayPictureScreen extends StatelessWidget {
  final String imagePath;

  const DisplayPictureScreen({Key key, this.imagePath}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Display the Picture')),
      // The image is stored as a file on the device. Use the `Image.file`
      // constructor with the given path to display the image.
      body: Center(
          child: Image.file(File(imagePath))
      ),
    );
  }
}

Conclusions

  • Easy to quickly get up and running with a barebones camera app
  • Advanced features are still missing from the camera plugin

Coming to a meetup near you...

How to build your own Flutter Plugin Package

Questions?

Flutter: Camera Basics

By Kenneth Baldauf

Flutter: Camera Basics

Flutter is Google's cross platform mobile application framework; let's take a look at how to interface with your camera via a Flutter app.

  • 472