DEV Community

loading...
Cover image for Building a Camera App with Flutter.

Building a Camera App with Flutter.

samuelezedi profile image Samuel Ezedi ・5 min read

Hi Flutter friends, today I want to show you how to build a simple camera application with flutter. So sit tight, “get your code editor” and let's begin.
How will it work?🤔

This is how our simple camera will work. When the user takes a picture, the camera redirects the user to a preview page where he can share it with friends or contacts. Basically we will have two screens, the camera screen and, the preview screen. In the Camera screen, we’ll have two buttons, a capture button and toggle button(to switch the camera direction— front or back). Also in the preview screen, we only need a share icon button by which when tapped prompts a share widget.

First things first.

We’ll need a couple of packages as we build, so head on to pub.dev and get these packages below:

// add theses packages
  camera: ^0.5.8+2
  esys_flutter_share: ^1.0.2
  path: ^1.6.4
  path_provider: ^1.6.11

Make sure to run:

flutter pub get

👍Now head over to AndroidManifest.xml. You can find this file in app/src/main. Paste the below permission inside your AndroidManifest.xml file just above the application tag:

// paste this in
    <uses-permission android:name="android.permission.CAMERA" />
    //

👍In the lib folder, create a new folder named screens. Inside it, create a new file and name it camera.dart.
Create a Stateful widget inside the camera.dart file like this below and make sure to import material.dart:

import 'package:flutter/material.dart';

class CameraScreen extends StatefulWidget {
  @override
  _CameraScreenState createState() => _CameraScreenState();
}

class _CameraScreenState extends State<CameraScreen> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

The first thing we will need to do is to define the following:
CameraController: this is responsible for establishing a connection to the device camera.
List cameras: this will hold a list of all the cameras available on the device. Usually, the length of the list is 2, which is the front and back camera. Index 0 is for the back and index 1 is for the front.
int selectedCameraIndex: this will hold the current camera index the user has selected.
Then we’ll create a method to initialize the camera object which should be done asynchronously hence the return type would be a future.

CameraController cameraController;
List cameras;
int selectedCameraIndex;

Future initCamera(CameraDescription cameraDescription) async {
    if (cameraController != null) {
      await cameraController.dispose();
    }

    cameraController =
        CameraController(cameraDescription, ResolutionPreset.high);

    cameraController.addListener(() {
      if (mounted) {
        setState(() {});
      }
    });

    if (cameraController.value.hasError) {
      print('Camera Error ${cameraController.value.errorDescription}');
    }

    try {
      await cameraController.initialize();
    } catch (e) {
       String errorText = 'Error ${e.code} \nError message: ${e.description}';
    }

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

Camera Preview Widget

Now, we’ll create a cameraPreview: this will return a CameraPreview widget if the camera controller object is initialized successfully else it will return a loading screen.

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

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

Camera Control Widget

Next, we’ll create a camera control widget, this widget is responsible for taking pictures when tapped it captures what the camera is viewing.

Widget cameraControl(context) {
    return Expanded(
      child: Align(
        alignment: Alignment.center,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          mainAxisSize: MainAxisSize.max,
          children: <Widget>[
            FloatingActionButton(
              child: Icon(
                Icons.camera,
                color: Colors.black,
              ),
              backgroundColor: Colors.white,
              onPressed: () {
                onCapture(context);
              },
            )
          ],
        ),
      ),
    );
  }

Camera Toggle

This widget is responsible for holding the toggle button, which can be used to toggle between the front and back cameras.

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

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

    return Expanded(
      child: Align(
        alignment: Alignment.centerLeft,
        child: FlatButton.icon(
            onPressed: () {
              onSwitchCamera();
            },
            icon: Icon(
              getCameraLensIcons(lensDirection),
              color: Colors.white,
              size: 24,
            ),
            label: Text(
              '${lensDirection.toString().substring(lensDirection.toString().indexOf('.') + 1).toUpperCase()}',
              style:
                  TextStyle(color: Colors.white, fontWeight: FontWeight.w500),
            )),
      ),
    );
  }

Now find below every other widget needed in camera.dart file before we dive into the build method. Also, look closely to see the initState function. In the initState function we call the initCamera function inside a future method which checks for device available cameras.

@override
  void initState() {
    // TODO: implement initState
    super.initState();
    availableCameras().then((value) {
      cameras = value;
      if(cameras.length > 0){
        setState(() {
          selectedCameraIndex = 0;
        });
        initCamera(cameras[selectedCameraIndex]).then((value) {

        });
      } else {
        print('No camera available');
      }
    }).catchError((e){
      print('Error : ${e.code}');
    });
  }

onCapture(context) async {
    try {
      final p = await getTemporaryDirectory();
      final name = DateTime.now();
      final path = "${p.path}/$name.png";

      await cameraController.takePicture(path).then((value) {
        print('here');
        print(path);
        Navigator.push(context, MaterialPageRoute(builder: (context) =>PreviewScreen(imgPath: path,fileName: "$name.png",)));
      });

    } catch (e) {
      showCameraException(e);
    }
  }

getCameraLensIcons(lensDirection) {
    switch (lensDirection) {
      case CameraLensDirection.back:
        return CupertinoIcons.switch_camera;
      case CameraLensDirection.front:
        return CupertinoIcons.switch_camera_solid;
      case CameraLensDirection.external:
        return CupertinoIcons.photo_camera;
      default:
        return Icons.device_unknown;
    }
  }

  onSwitchCamera() {
    selectedCameraIndex =
        selectedCameraIndex < cameras.length - 1 ? selectedCameraIndex + 1 : 0;
    CameraDescription selectedCamera = cameras[selectedCameraIndex];
    initCamerar(selectedCamera);
  }

The Build Widget

Take a critical look at the build method below, we have created a Scaffold widget which a child of Container. The Container has a child of Stack. Inside the Stack, we’ll add two Align widgets. The first Align widget should have an alignment of center and a child of cameraPreview(). The second Align widget will have an alignment of bottomCenter and a child of Row. The Row then takes in two widgets which are cameraToggle() and cameraControl().

@override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: Container(
        child: Stack(
          children: <Widget>[
            Align(
              alignment: Alignment.center,
              child: cameraPreview(),
            ),
            Align(
              alignment: Alignment.bottomCenter,
              child: Container(
                height: 120,
                width: double.infinity,
                padding: EdgeInsets.all(15),
                color: Colors.transparent,
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.start,
                  children: <Widget>[
                    cameraToggle(),
                    cameraControl(context),
                    Spacer(),
                  ],
                ),
              ),
            )
          ],
        ),
      ),
    );
  }

Preview Page

We are now done with our Camera Page but left with one more page, the preview page.
Now create a new file inside the screens folder and name it preview.dart. and paste the code below inside it.

import 'dart:io';
import 'dart:typed_data';

import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class PreviewScreen extends StatefulWidget {
  final String imgPath;
  final String fileName;
  PreviewScreen({this.imgPath, this.fileName});

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

class _PreviewScreenState extends State<PreviewScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        automaticallyImplyLeading: true,
      ),
      body: Container(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            Expanded(
              flex: 2,
              child: Image.file(File(widget.imgPath),fit: BoxFit.cover,),
            ),
            Align(
              alignment: Alignment.bottomCenter,
              child: Container(
                width: double.infinity,
                height: 60,
                color: Colors.black,
                child: Center(
                  child: IconButton(
                    icon: Icon(Icons.share,color: Colors.white,),
                    onPressed: (){
                      getBytes().then((bytes) {
                        print('here now');
                        print(widget.imgPath);
                        print(bytes.buffer.asUint8List());
                        Share.file('Share via', widget.fileName, bytes.buffer.asUint8List(), 'image/path');
                      });
                    },
                  ),
                ),
              ),
            )
          ],
        ),
      )
    );
  }

  Future getBytes () async {
    Uint8List bytes = File(widget.imgPath).readAsBytesSync() as Uint8List;
    return ByteData.view(bytes.buffer);
  }
}

Now run the app🎉 congratulations you’ve successfully built your first camera app. Also, remember to add this to your resume 😉

You can get the full source code here:
Github

That’s it for this article! I hope you enjoyed it, and be sure to follow me here Flutter articles and comment for any feedback you might have about this article.

Discussion (5)

pic
Editor guide
Collapse
srk2k13 profile image
srk2k19 • Edited

How to use camera_screen.dart and camera.dart to capture image and upload to firebase storage in flutter? Ho can I go back to my place holder and circular avatar and place the captured image there
please contact me and fix my issue

First my application will have a circular avatar , on click of it it goes to camera_screen.dart using camera.dart and then shows preview , from here I want my image captured to be placed in my circular avatar main page please help me.

Collapse
thegroo profile image
Marcos Maia

Nice one. Thanks.

Collapse
srk2k13 profile image
srk2k19

How to use camera_screen.dart and camera.dart to capture image and upload to firebase storage in flutter? Ho can I go back to my place holder and circular avatar and place the captured image there

Collapse
stephancapistrano profile image
stephancapistrano

Hello. I'm trying to use de ultraHigh resolution (3840x2160), but the max resolution I get is 1920x1080. I'm running the app on Samsung A10S. How do I get a better resolution?

Collapse
faakhy profile image
Rémy | FakeRunner

Did you resolved this problem ?