Using the Camera on Flutter

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

Leave a rating/review
Download materials
Save for later
Share

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.