Flutter Camera & Image Picker: Complete Photo/Video App

by Didin J. on Jun 14, 2026 Flutter Camera & Image Picker: Complete Photo/Video App

Build a complete Flutter Camera & Image Picker app with photo capture, video recording, gallery selection, media preview, and permissions.

Flutter makes it easy to access device cameras, capture photos, record videos, and select media from the gallery. In this tutorial, you'll build a complete Flutter media application that combines camera functionality and image/video selection using the latest Flutter packages.

By the end of this tutorial, you'll learn how to:

  • Capture photos using the device camera
  • Record videos
  • Pick images from the gallery
  • Pick videos from the gallery
  • Display media previews
  • Handle permissions properly
  • Save and manage captured files

What We'll Build

The application will include:

  • Take Photo button
  • Record Video button
  • Pick an image from the gallery
  • Pick a video from the gallery
  • Media Preview Screen
  • Responsive Material 3 UI

Prerequisites

Before starting, ensure you have:

  • Flutter SDK 3.35+
  • Dart 3.9+
  • Android Studio or VS Code
  • Android Emulator or Physical Device
  • Xcode (for iOS development)


Step 1. Create a New Flutter Project

Create a new Flutter application.

flutter create flutter_camera_image_picker
cd flutter_camera_image_picker

Open the project in your favorite editor.

code .


Step 2. Add Required Dependencies

Open pubspec.yaml and add the following packages:

dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_lints: ^6.0.0
  camera: ^0.11.2+1
  image_picker: ^1.2.0
  path_provider: ^2.1.5
  path: ^1.9.1

Install the packages:

flutter pub get

Package Overview

Package Purpose
camera Camera preview, photo capture, video recording
image_picker Select images and videos from the gallery
path_provider Access application storage
path File path manipulation


Step 3. Configure Android Permissions

Open:

android/app/src/main/AndroidManifest.xml

Add permissions before the <application> tag:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

Inside the <application> tag add:

<uses-feature
    android:name="android.hardware.camera"
    android:required="false" />


Step 4. Configure iOS Permissions

Open:

ios/Runner/Info.plist

Add:

<key>NSCameraUsageDescription</key>
<string>This app needs camera access to take photos and record videos.</string>

<key>NSMicrophoneUsageDescription</key>
<string>This app needs microphone access for video recording.</string>

<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs access to your photo library.</string>

<key>NSPhotoLibraryAddUsageDescription</key>
<string>This app saves photos and videos to your library.</string>


Step 5. Create the Media Service

Create a new file:

lib/services/media_service.dart

Add the following code:

import 'package:camera/camera.dart';
import 'package:image_picker/image_picker.dart';

class MediaService {
  final ImagePicker _picker = ImagePicker();

  Future<XFile?> pickImage() async {
    return await _picker.pickImage(
      source: ImageSource.gallery,
      imageQuality: 90,
    );
  }

  Future<XFile?> pickVideo() async {
    return await _picker.pickVideo(
      source: ImageSource.gallery,
    );
  }
}

This service handles media selection from the gallery.


Step 6. Create the Camera Screen

Replace the contents of lib/main.dart.

Import Dependencies

import 'dart:io';

import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final cameras = await availableCameras();

  runApp(const MyApp(cameras: cameras));
}

Create Application Root

class MyApp extends StatelessWidget {
  final List<CameraDescription> cameras;

  const MyApp({
    super.key,
    required this.cameras,
  });

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Media App',
      theme: ThemeData(
        colorSchemeSeed: Colors.blue,
        useMaterial3: true,
      ),
      home: CameraHomePage(cameras: cameras),
    );
  }
}

Create Camera Home Page

class CameraHomePage extends StatefulWidget {
  final List<CameraDescription> cameras;

  const CameraHomePage({
    super.key,
    required this.cameras,
  });

  @override
  State<CameraHomePage> createState() => _CameraHomePageState();
}

State Variables

class _CameraHomePageState extends State<CameraHomePage> {
  late CameraController _controller;
  bool _isInitialized = false;
  bool _isRecording = false;

  XFile? _selectedImage;
  XFile? _selectedVideo;

Initialize Camera

  @override
  void initState() {
    super.initState();
    _initializeCamera();
  }

  Future<void> _initializeCamera() async {
    _controller = CameraController(
      widget.cameras.first,
      ResolutionPreset.high,
    );

    await _controller.initialize();

    if (mounted) {
      setState(() {
        _isInitialized = true;
      });
    }
  }

Capture Photo

  Future<void> _takePhoto() async {
    if (!_controller.value.isInitialized) return;

    final photo = await _controller.takePicture();

    setState(() {
      _selectedImage = photo;
      _selectedVideo = null;
    });
  }

Record Video

  Future<void> _recordVideo() async {
    if (_isRecording) {
      final video = await _controller.stopVideoRecording();

      setState(() {
        _selectedVideo = video;
        _selectedImage = null;
        _isRecording = false;
      });
    } else {
      await _controller.startVideoRecording();

      setState(() {
        _isRecording = true;
      });
    }
  }

Pick Image

  Future<void> _pickImage() async {
    final picker = ImagePicker();

    final image = await picker.pickImage(
      source: ImageSource.gallery,
      imageQuality: 90,
    );

    if (image != null) {
      setState(() {
        _selectedImage = image;
        _selectedVideo = null;
      });
    }
  }

Pick Video

  Future<void> _pickVideo() async {
    final picker = ImagePicker();

    final video = await picker.pickVideo(
      source: ImageSource.gallery,
    );

    if (video != null) {
      setState(() {
        _selectedVideo = video;
        _selectedImage = null;
      });
    }
  }

Dispose Resources

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

Build UI

  @override
  Widget build(BuildContext context) {
    if (!_isInitialized) {
      return const Scaffold(
        body: Center(
          child: CircularProgressIndicator(),
        ),
      );
    }

    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Camera App'),
      ),
      body: SingleChildScrollView(
        child: Column(
          children: [
            AspectRatio(
              aspectRatio: _controller.value.aspectRatio,
              child: CameraPreview(_controller),
            ),

            const SizedBox(height: 16),

            Wrap(
              spacing: 10,
              runSpacing: 10,
              alignment: WrapAlignment.center,
              children: [
                FilledButton.icon(
                  onPressed: _takePhoto,
                  icon: const Icon(Icons.camera_alt),
                  label: const Text('Take Photo'),
                ),

                FilledButton.icon(
                  onPressed: _recordVideo,
                  icon: Icon(
                    _isRecording
                        ? Icons.stop
                        : Icons.videocam,
                  ),
                  label: Text(
                    _isRecording
                        ? 'Stop Recording'
                        : 'Record Video',
                  ),
                ),

                OutlinedButton.icon(
                  onPressed: _pickImage,
                  icon: const Icon(Icons.image),
                  label: const Text('Gallery Image'),
                ),

                OutlinedButton.icon(
                  onPressed: _pickVideo,
                  icon: const Icon(Icons.video_library),
                  label: const Text('Gallery Video'),
                ),
              ],
            ),

            const SizedBox(height: 24),

            if (_selectedImage != null)
              Column(
                children: [
                  const Text(
                    'Selected Image',
                    style: TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                  ),

                  const SizedBox(height: 12),

                  Image.file(
                    File(_selectedImage!.path),
                    height: 300,
                  ),
                ],
              ),

            if (_selectedVideo != null)
              Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  children: [
                    const Icon(
                      Icons.video_file,
                      size: 80,
                    ),
                    const SizedBox(height: 10),
                    Text(
                      _selectedVideo!.name,
                      textAlign: TextAlign.center,
                    ),
                  ],
                ),
              ),
          ],
        ),
      ),
    );
  }
}


Step 7. Run the Application

Run the application:

flutter run

You should see:

  • Live camera preview
  • Photo capture functionality
  • Video recording functionality
  • Gallery image picker
  • Gallery video picker
  • Media preview area


Understanding the Camera Workflow

The Flutter camera package works in three steps:

1. Get Available Cameras

final cameras = await availableCameras();

2. Create Controller

CameraController(
  camera,
  ResolutionPreset.high,
);

3. Initialize Camera

await controller.initialize();

Only after initialization can the preview and capture functions be used.


Adding Front Camera Support

To switch between front and rear cameras:

int _cameraIndex = 0;

Add:

Future<void> switchCamera() async {
  _cameraIndex =
      (_cameraIndex + 1) %
      widget.cameras.length;

  await _controller.dispose();

  _controller = CameraController(
    widget.cameras[_cameraIndex],
    ResolutionPreset.high,
  );

  await _controller.initialize();

  setState(() {});
}

Add a button:

IconButton(
  onPressed: switchCamera,
  icon: const Icon(Icons.flip_camera_ios),
)


Improving User Experience

For production applications, consider:

Image Compression

await picker.pickImage(
  imageQuality: 80,
);

Limit Video Duration

await picker.pickVideo(
  source: ImageSource.gallery,
  maxDuration: Duration(minutes: 2),
);

Save Files to App Storage

Use path_provider:

final directory =
    await getApplicationDocumentsDirectory();

Display Video Preview

Add:

video_player: ^2.10.0

This allows users to play recorded videos directly inside the app.


Common Issues and Solutions

Camera Preview Black Screen

Make sure:

await controller.initialize();

has completed before rendering the preview.

Permission Denied

Verify Android and iOS permission configurations are correct.

Emulator Camera Not Working

Use a physical device whenever possible. Many emulators provide limited camera support.

Video Recording Fails

Ensure microphone permission is granted:

<uses-permission android:name="android.permission.RECORD_AUDIO" />


>Conclusion

In this tutorial, you've built a complete Flutter photo and video application using the latest Flutter ecosystem. You learned how to integrate the camera package for live camera previews, photo capture, and video recording, while also leveraging image_picker to select media from the device gallery.

You also explored permission handling, media previews, camera initialization, front/rear camera switching, and several production-ready enhancements. These techniques provide a solid foundation for building social media apps, profile image upload systems, messaging applications, content creation tools, and any Flutter app that requires rich media functionality.

From here, you can extend the project by adding video playback, image editing, cloud uploads, offline storage, AI-powered image analysis, or integration with Firebase Storage and Supabase Storage to create a fully featured media management application.

You can find the full source code on our GitHub.

We know that building beautifully designed Mobile and Web Apps from scratch can be frustrating and very time-consuming. Check Envato unlimited downloads and save development and design time.

That's just the basics. If you need more deep learning about Flutter and Dart, you can take the following cheap course:

Thanks!