Using the Camera on Flutter
See how to integrate the device camera into your Flutter app for iOS and Android. By Sagar Suri.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Using the Camera on Flutter
15 mins
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.
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:
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:
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:
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:
As you can see, beyond the usual dependencies, there are four libraries that have added to the project:
- camera: A Flutter plugin for iOS and Android allowing access to the device cameras.
- path_provider: A Flutter plugin for finding commonly used locations on the filesystem. Supports both iOS and Android.
- path: A comprehensive, cross-platform path manipulation library for Dart.
- 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:
Choose the import option to add the necessary import.
You must be curious to know about these four properties you just added:
-
CameraController controller
: This class is responsible for establishing a connection to the device’s camera. -
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. The0
index is for the back camera and the1
index for the front camera. -
selectedCameraIdx
: This will hold the current camera index that the user has selected. -
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:
-
availableCameras()
is part of thecamera
plugin which will return a list of available cameras on the device. - Initially,
selectedCameraIdx
will be0
, as you will be loading the back camera at every cold launch of the app. You can change the value from0
to1
if the front camera is your initial preference. - 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:
-
_initCameraController
is responsible for initializing theCameraController
object. Initializing aCameraController
object is asynchronous work. Hence the return type of this method is aFuture
. -
CameraDescription
will hold the type of camera(front or back) you want to use. - You are creating a
CameraController
object which takes two arguments, first acameraDescription
and second aresolutionPreset
with which the picture should be captured.ResolutionPreset
can have only 3 values i.ehigh
,medium
andlow
. -
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. -
mounted
is a getter method which will return a boolean value indicating whether theCameraScreenState
object is currently in the widget tree or not. - While initializing the
controller
object if something goes wrong you will catch the error in atry/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.