Getting a list of available cameras

First, we will get the lists of cameras using the camera plugin.

List<CameraDescription> _cameras; @override

void initState() {

_initCamera();

super.initState();

} Future<void> _initCamera() async {

_cameras = await availableCameras();

}

Initializing camera controller

Now we have a list of available cameras. Next, we will initialize the camera controller. The camera controller is used to control the device cameras. CameraController takes two values CameraDescription and ResolutionPreset . Initially, we have given a camera description as _camera[0] which is our back camera.

Note: Here we have given ResolutionPreset as the medium. Try avoiding going to a higher resolution if it freezes your camera. Look at this issue for more detail.

CameraController _controller;



Future<void> _initCamera() async {

_controller = CameraController(_cameras[0], ResolutionPreset.medium);

_controller.initialize().then((_) {

if (!mounted) {

return;

}

setState(() {});

});

}



@override

void dispose() {

_controller?.dispose();

super.dispose();

}

Camera Preview

Once our camera is all set up we will show the preview feed using CameraPreview widget. Before we show the camera preview we must wait for the CameraController to initialize.

@override

Widget build(BuildContext context) {

if (_controller != null) {

if (!_controller.value.isInitialized) {

return Container();

}

} else {

return const Center(

child: SizedBox(

width: 32,

height: 32,

child: CircularProgressIndicator(),

),

);

}

}

Once the camera is initialized we will show the camera preview.

return Scaffold(

backgroundColor: Theme.of(context).backgroundColor,

key: _scaffoldKey,

extendBody: true,

body: Stack(

children: <Widget>[

_buildCameraPreview(),

],

),

);

Inside the _buildCameraPreview() we are scaling the camera preview to the screen size to make it look full screen.

Widget _buildCameraPreview() {

final size = MediaQuery.of(context).size;

return ClipRect(

child: Container(

child: Transform.scale(

scale: _controller.value.aspectRatio / size.aspectRatio,

child: Center(

child: AspectRatio(

aspectRatio: _controller.value.aspectRatio,

child: CameraPreview(_controller),

),

),

),

),

);

}

Switch camera

The next step is to have the ability to switch or toggle between front and back cameras. To do that let’s first add the icon button to your stack widget.

body: Stack(

children: <Widget>[

_buildCameraPreview(),

Positioned(

top: 24.0,

left: 12.0,

child: IconButton(

icon: Icon(

Icons.switch_camera,

color: Colors.white,

),

onPressed: _onCameraSwitch,

),

),

],

),

This icon button calls the method _onCameraSwitch when pressed. In this method, we will first dispose of the CameraController and then initialize the new CameraController with a new CameraDescription .

Future<void> _onCameraSwitch() async {

final CameraDescription cameraDescription =

(_controller.description == _cameras[0]) ? _cameras[1] : _cameras[0];

if (_controller != null) {

await _controller.dispose();

}

_controller = CameraController(cameraDescription, ResolutionPreset.medium);

_controller.addListener(() {

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

if (_controller.value.hasError) {

showInSnackBar('Camera error ${_controller.value.errorDescription}');

}

});



try {

await _controller.initialize();

} on CameraException catch (e) {

_showCameraException(e);

}



if (mounted) {

setState(() {});

}

}

Camera Control view

At the bottom of the screen, we will have a control view which will basically contain 3 buttons. First to go to the gallery, second to capture images or record video, and third to switch between image capture and video recording.

return Scaffold(

backgroundColor: Theme.of(context).backgroundColor,

key: _scaffoldKey,

extendBody: true,

body: ...

bottomNavigationBar: _buildBottomNavigationBar(),

);

The view will be shown in the bottom navigation bar. Don’t forget to add extendBody: true.

Widget _buildBottomNavigationBar() {

return Container(

color: Theme.of(context).bottomAppBarColor,

height: 100.0,

width: double.infinity,

child: Row(

mainAxisAlignment: MainAxisAlignment.spaceAround,

children: <Widget>[

FutureBuilder(

future: getLastImage(),

builder: (context, snapshot) {

if (snapshot.data == null) {

return Container(

width: 40.0,

height: 40.0,

);

}

return GestureDetector(

onTap: () => Navigator.push(

context,

MaterialPageRoute(

builder: (context) => Gallery(),

),

),

child: Container(

width: 40.0,

height: 40.0,

child: ClipRRect(

borderRadius: BorderRadius.circular(4.0),

child: Image.file(

snapshot.data,

fit: BoxFit.cover,

),

),

),

);

},

),

CircleAvatar(

backgroundColor: Colors.white,

radius: 28.0,

child: IconButton(

icon: Icon(

(_isRecordingMode)

? (_isRecording) ? Icons.stop : Icons.videocam

: Icons.camera_alt,

size: 28.0,

color: (_isRecording) ? Colors.red : Colors.black,

),

onPressed: () {

if (!_isRecordingMode) {

_captureImage();

} else {

if (_isRecording) {

stopVideoRecording();

} else {

startVideoRecording();

}

}

},

),

),

IconButton(

icon: Icon(

(_isRecordingMode) ? Icons.camera_alt : Icons.videocam,

color: Colors.white,

),

onPressed: () {

setState(() {

_isRecordingMode = !_isRecordingMode;

});

},

),

],

),

);

}

Capturing an Image

Capturing an image is pretty easy with the camera controller.

Check if the camera controller is initialized. Construct a directory and defines the path. Capture the image using CameraController and save it to the given path.

void _captureImage() async {

if (_controller.value.isInitialized) {

final Directory extDir = await getApplicationDocumentsDirectory();

final String dirPath = '${extDir.path}/media';

await Directory(dirPath).create(recursive: true);

final String filePath = '$dirPath/${_timestamp()}.jpeg';

await _controller.takePicture(filePath);

setState(() {});

}

}

Recording a video

We can divide the recording video process into two parts:

Start video recording:

Check if the camera controller is initialized. Start timer to show the recorded video time. (optional) Construct a directory and defines the path. Start recording using the camera controller and saving the video on the defined path.

Future<String> startVideoRecording() async {

print('startVideoRecording');

if (!_controller.value.isInitialized) {

return null;

}

setState(() {

_isRecording = true;

});

_timerKey.currentState.startTimer();



final Directory extDir = await getApplicationDocumentsDirectory();

final String dirPath = '${extDir.path}/media';

await Directory(dirPath).create(recursive: true);

final String filePath = '$dirPath/${_timestamp()}.mp4';



if (_controller.value.isRecordingVideo) {

// A recording is already started, do nothing.

return null;

}



try {

await _controller.startVideoRecording(filePath);

} on CameraException catch (e) {

_showCameraException(e);

return null;

}

return filePath;

}

Stop video recording:

Check if the camera controller is initialized. Stop timer. Stop video recording using the camera controller.

Future<void> stopVideoRecording() async {

if (!_controller.value.isRecordingVideo) {

return null;

}

_timerKey.currentState.stopTimer();

setState(() {

_isRecording = false;

});



try {

await _controller.stopVideoRecording();

} on CameraException catch (e) {

_showCameraException(e);

return null;

}

}

Here is the full code for the camera screen.