Flutter Beta

Learn cross-platform development with our collection of trusted high-quality Flutter tutorials!

Using the Camera on Flutter

See how to integrate the device camera into your Flutter app for iOS and Android.

5/5 1 Rating

Version

  • Dart 2, Flutter 1.7, Android Studio 3.5

In today’s world, mobile cameras are the new definition of communication and entertainment. People use the camera to capture still photos or videos to share with friends and family, upload selfies or videos to social network platforms, make video calls, and more.

When it comes to mobile, almost every app out there uses the camera to perform various tasks, and if you are reading this tutorial then maybe you have a multi-million dollar app idea which depends on the camera or you want to learn how to integrate camera in a Flutter app.

In this tutorial, you’ll be building a Flutter app which will use the device’s camera (on Android or iOS) to display a preview, take a photo, and share it with your friends.

Introduction

Before you start building the app, let me give you a brief description of what it feels like to access the camera in Android or iOS natively.

On Android, if you want to access the device camera you need to create a SurfaceView to render previews of what the camera sensor is picking up. This requires a deeper understanding of the SurfaceView to properly control the camera hardware. On iOS, you need to know about the AVFoundation Capture subsystem that provides a high-level architecture to build a custom camera UI.

If you are just getting started with mobile app development, then it will be quite difficult to understand both those APIs. Not only that, you also need to know both the Kotlin and Swift programming languages to write separate apps for Android and iOS.

But with Flutter, you don’t have to worry about all that: With one codebase you can build an app that can accesses the camera hardware on both Android and iOS. Isn’t that amazing? :]

So in this tutorial, you will make an app that will open up the camera preview on launch. You can switch between the front and back camera, click a picture, and share it with your friends. While building this app you will learn how to use the camera package built by the Flutter team, save the image to a path, and share the image to different social platforms.

Note: This tutorial assumes prior knowledge of Dart and the Flutter framework for developing cross-platform mobile apps. If you are unfamiliar with Flutter, please see Getting Started with Flutter to get yourself familiar with Flutter application development. You’ll also need a physical Android or iOS to fully follow along.

Getting Started

Download the starter project using the Download Materials at the top or bottom of the tutorial. The starter project contains the required libraries and some boilerplate code. The basic UI for this app is already built so that you can focus on how to integrate the device camera.

Fire up Android Studio 3.4 or later with the Flutter plugin installed, and choose the option Open an existing Android Studio project. Select the starter project which you just downloaded. Image for reference:

Android Studio Welcome screen

After opening the project, click Get dependencies on the Packages get message near the top of Android Studio, to pull down the project dependencies.

Before you make any changes to the starter project, run it to see the current state of the app. If you press the green Run button in Android Studio, you should be able to run the app and see the following screen on your mobile phone, iOS Simulator, or Android emulator:

Starter Project

Exploring the Project

Once you have run the starter project it’s time to take a look at the project structure, expand the lib folder and check the folders within it. It should look like this:

lib folders

camera_screen.dart is the camera preview screen where you can click a picture and toggle between front or back camera, preview_screen.dart is the screen where you will see the preview of the image you clicked and will have the option to share that image with your friends. Finally, main.dart is the root widget of your app.

Open the pubspec.yaml file to see all the dependencies required for the app. It should look like this:

pubspec.yaml file

As you can see, beyond the usual dependencies, there are four libraries that have added to the project:

  1. camera: A Flutter plugin for iOS and Android allowing access to the device cameras.
  2. path_provider: A Flutter plugin for finding commonly used locations on the filesystem. Supports both iOS and Android.
  3. path: A comprehensive, cross-platform path manipulation library for Dart.
  4. esys_flutter_share: A Flutter plugin for sharing files and text with other applications.

Coding the Camera Screen

It’s time to look at all the Dart files one by one.

Open the main.dart file:

import 'package:flutter/material.dart';
import 'camerascreen/camera_screen.dart';

class CameraApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: CameraScreen(),
    );
  }
}

void main() => runApp(CameraApp());

CameraApp is the root widget. This is the entry point for your app. Its responsibility is to launch the CameraScreen widget where you will see the camera preview.

Next, open the camera_screen.dart file. This is a StatefulWidget, because you will be adding the feature to toggle between the front and back camera (changing the state of the camera). If you go through the file you will find some TODO’s which you will be addressing soon.

Going step by step, first add four properties to the _CameraScreenState class as shown below:

class _CameraScreenState extends State {
  CameraController controller;
  List cameras;
  int selectedCameraIdx;
  String imagePath;

You might see a red squiggly error line under CameraController. If not, that’s because the needed import is already in the starter project file. But if so, you need to import the camera package to make the error go away. Click on the line and press option+return on macOS or Alt+Enter on PC. You will see a menu popup:

import popup

Choose the import option to add the necessary import.

You must be curious to know about these four properties you just added:

  1. CameraController controller: This class is responsible for establishing a connection to the device’s camera.
  2. List cameras: This will hold a list of the cameras available on the device. Normally, the size of the list will be 2 i.e. a front and back camera. The 0 index is for the back camera and the 1 index for the front camera.
  3. selectedCameraIdx: This will hold the current camera index that the user has selected.
  4. imagePath: This will hold the path of the image which you will create using the camera.

Next, override the initState() method and add the following code to it:

@override
void initState() {
  super.initState();
  // 1
  availableCameras().then((availableCameras) {
    
    cameras = availableCameras;
    if (cameras.length > 0) {
      setState(() {
        // 2
        selectedCameraIdx = 0;
      });

      _initCameraController(cameras[selectedCameraIdx]).then((void v) {});
    }else{
      print("No camera available");
    }
  }).catchError((err) {
    // 3
    print('Error: $err.code\nError Message: $err.message');
  });
}

initState() is one of the lifecycle methods of a StatefulWidget. It will be called when CameraScreen is inserted into the widget tree.

In this code:

  1. availableCameras()is part of the camera plugin which will return a list of available cameras on the device.
  2. Initially, selectedCameraIdx will be 0, as you will be loading the back camera at every cold launch of the app. You can change the value from 0 to 1 if the front camera is your initial preference.
  3. In the process of getting the list of cameras if something goes wrong, the code execution will enter the catchError() function.

Now, create the missing method _initCameraController and add the following code to it:

// 1, 2
Future _initCameraController(CameraDescription cameraDescription) async {
  if (controller != null) {
    await controller.dispose();
  }

  // 3
  controller = CameraController(cameraDescription, ResolutionPreset.high);

  // If the controller is updated then update the UI.
  // 4
  controller.addListener(() {
    // 5
    if (mounted) {
      setState(() {});
    }

    if (controller.value.hasError) {
      print('Camera error ${controller.value.errorDescription}');
    }
  });

  // 6
  try {
    await controller.initialize();
  } on CameraException catch (e) {
    _showCameraException(e);
  }

  if (mounted) {
    setState(() {});
  }
}

Here is a step by step explanation:

  1. _initCameraController is responsible for initializing the CameraController object. Initializing a CameraController object is asynchronous work. Hence the return type of this method is a Future.
  2. CameraDescription will hold the type of camera(front or back) you want to use.
  3. You are creating a CameraController object which takes two arguments, first a cameraDescription and second a resolutionPreset with which the picture should be captured. ResolutionPreset can have only 3 values i.e highmediumand low.
  4. addListener() will be called when the controller object is changed. For example, this closure will be called when you switch between the front and back camera.
  5. mounted is a getter method which will return a boolean value indicating whether the CameraScreenState object is currently in the widget tree or not.
  6. While initializing the controller object if something goes wrong you will catch the error in a try/catch block.

Now you’ll complete the _cameraPreviewWidget() implementation. Replace the TODO and Container widget with the following code:

Widget _cameraPreviewWidget() {
  if (controller == null || !controller.value.isInitialized) {
    return const Text(
      'Loading',
      style: TextStyle(
        color: Colors.white,
        fontSize: 20.0,
        fontWeight: FontWeight.w900,
      ),
    );
  }

  return AspectRatio(
      aspectRatio: controller.value.aspectRatio,
      child: CameraPreview(controller),
    );
}

This will return a CameraPreview widget if the controller object is initialized successfully, or else a Text widget with the label ‘Loading’. The CameraPreview widget will return a camera view.

Displaying the Camera Preview

From this point you’ll need to be on a physical device. Run the project and you should see the following screen:

Camera preview

Amazing! You’ve just integrated the camera feature into your app!

But as you can see, there is no way to switch between the front and back camera, and if you press the capture button nothing happens. You’ll implement those features next.

Switching Cameras

Update _cameraTogglesRowWidget() to look as follows:

Widget _cameraTogglesRowWidget() {
  if (cameras == null || cameras.isEmpty) {
    return Spacer();
  }

  CameraDescription selectedCamera = cameras[selectedCameraIdx];
  CameraLensDirection lensDirection = selectedCamera.lensDirection;

  return Expanded(
    child: Align(
      alignment: Alignment.centerLeft,
      child: FlatButton.icon(
          onPressed: _onSwitchCamera,
          icon: Icon(_getCameraLensIcon(lensDirection)),
          label: Text(
              "${lensDirection.toString().substring(lensDirection.toString().indexOf('.') + 1)}")),
    ),
  );
}

In the above code, you are just showing an icon to the user which indicates the currently selected camera (front or back). On pressing the FlatButton you can toggle between front and back camera.

Now add the _getCameraLensIcon() method:

IconData _getCameraLensIcon(CameraLensDirection direction) {
  switch (direction) {
    case CameraLensDirection.back:
      return Icons.camera_rear;
    case CameraLensDirection.front:
      return Icons.camera_front;
    case CameraLensDirection.external:
      return Icons.camera;
    default:
      return Icons.device_unknown;
  }
}

_getCameraLensIcon() just returns a specific icon based on the camera selected.

Next, add the logic to toggle between the front and back camera. Create the method _onSwitchCamera():

void _onSwitchCamera() {
  selectedCameraIdx =
  selectedCameraIdx < cameras.length - 1 ? selectedCameraIdx + 1 : 0;
  CameraDescription selectedCamera = cameras[selectedCameraIdx];
  _initCameraController(selectedCamera);
}

In the above code, you are just changing the index, i.e. 0 for the back camera and 1 for the front camera. You are creating a new CameraDescription which will hold the camera object based on the updated index and then again initialize the CameraController object by calling _initCameraController().

Build and run the app to your device. Tap the icon at the bottom-left corner of the screen to toggle between the front and back camera. The screens should look as follows:

back camera

front camera

Taking a Picture

It’s time you start taking some awesome images using this camera app you’ve built!

Update the _onCapturePressed() to look as follows:

void _onCapturePressed(context) async {
  try {
    // 1
    final path = join(
      (await getTemporaryDirectory()).path,
      '${DateTime.now()}.png',
    );
    // 2
    await controller.takePicture(path);
    // 3
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => PreviewImageScreen(imagePath: path),
      ),
    );
  } catch (e) {
    print(e);
  }
}

You’ll see some errors at this point. You need to import the appropriate packages to make the errors go away. Click on each red line and use option+return or Alt+Enter to import the packages.

The above code step-by-step is as follows:

  1. join(..) will take different paths and join them to make a final path. For example: p.join('path', 'to', 'foo'); // -> 'path/to/foo'
  2. takePicture(path) will capture a raw image and write it to the path provided as an argument.
  3. After the picture is saved successfully to the specified path, you navigate to PreviewImageScreen to preview the image and share it with your friends.

Run the app and you will be able to take a picture, which will navigate to the PreviewScreen:

Preview screen

 Sharing the Image

If you click on the Share Button you will be presented with a Bottomsheet Dialog through which you can share the image you have just clicked.

Share screen

Open the preview_screen.dart file in order to see the code behind the share button.

The getBytesFromFile() method converts the image file into ByteData:

Future<ByteData> getBytesFromFile() async {
  Uint8List bytes = File(widget.imagePath).readAsBytesSync() as Uint8List;
  return ByteData.view(bytes.buffer);
}

The onPressed handler for the share button calls getBytesFromFile().

onPressed: () {
  getBytesFromFile().then((bytes) {
    Share.file('Share via:', basename(widget.imagePath),
        bytes.buffer.asUint8List(), 'image/png');
  });
},

After that call returns, the bytes from the file are passed to the file() method of Share class. The Share class is a part of esys_flutter_share plugin which you already added in the starter project.

You’ve successfully integrated the camera, and it’s time to play around with the app you just created. Take some amazing pictures and share them with your friends to demonstrate your photography skills!

Happy camera face

Where to Go From Here?

Check out the final completed project by clicking the Download Materials button at the top of bottom of the tutorial.

You can read more about using a camera with Flutter in the official Flutter documentation.

You can explore more about the various plugins we just used in this tutorial:

Try adding them to other Flutter projects by yourself.

I hope you enjoyed this tutorial on integrating the camera into your Flutter app! Feel free to share your feedback, findings or ask any questions in the comments below or in the forums.

Average Rating

5/5

Add a rating for this content

1 rating

Contributors

Comments