Integrate Camera Functionality
Adding camera functionality to your Flutter app allows users to capture photos and videos directly within your application. This tutorial will guide you through implementing a feature-rich camera interface with various options and controls.
Features
- Camera preview
- Photo capture
- Video recording
- Flash control
- Camera switching
- Focus control
- Image saving
- Permission handling
Implementation Steps
-
Setup Dependencies
# pubspec.yaml dependencies: camera: ^0.10.5+9 path_provider: ^2.1.2 path: ^1.8.3 permission_handler: ^11.2.0 provider: ^6.1.1
-
Configure Platform Settings
<!-- android/app/src/main/AndroidManifest.xml --> <manifest ...> <uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" /> </manifest>
<!-- ios/Runner/Info.plist --> <dict> <key>NSCameraUsageDescription</key> <string>This app needs camera access to take photos</string> <key>NSMicrophoneUsageDescription</key> <string>This app needs microphone access for video recording</string> </dict>
-
Create Camera Service
class CameraService { CameraController? _controller; List<CameraDescription>? _cameras; bool _isInitialized = false; CameraController? get controller => _controller; List<CameraDescription>? get cameras => _cameras; bool get isInitialized => _isInitialized; Future<void> initialize() async { _cameras = await availableCameras(); if (_cameras == null || _cameras!.isEmpty) { throw Exception('No cameras available'); } _controller = CameraController( _cameras![0], ResolutionPreset.high, enableAudio: true, ); await _controller!.initialize(); _isInitialized = true; } Future<void> switchCamera() async { if (_cameras == null || _cameras!.length < 2) return; final currentIndex = _cameras!.indexOf(_controller!.description); final newIndex = (currentIndex + 1) % _cameras!.length; _controller = CameraController( _cameras![newIndex], ResolutionPreset.high, enableAudio: true, ); await _controller!.initialize(); } Future<void> setFlashMode(FlashMode mode) async { if (_controller == null || !_isInitialized) return; await _controller!.setFlashMode(mode); } Future<String?> takePicture() async { if (_controller == null || !_isInitialized) return null; try { final XFile photo = await _controller!.takePicture(); return photo.path; } catch (e) { print('Error taking picture: $e'); return null; } } Future<String?> startVideoRecording() async { if (_controller == null || !_isInitialized) return null; try { await _controller!.startVideoRecording(); return null; } catch (e) { print('Error starting video recording: $e'); return null; } } Future<String?> stopVideoRecording() async { if (_controller == null || !_isInitialized) return null; try { final XFile video = await _controller!.stopVideoRecording(); return video.path; } catch (e) { print('Error stopping video recording: $e'); return null; } } void dispose() { _controller?.dispose(); } }
-
Create Camera Provider
class CameraProvider extends ChangeNotifier { final CameraService _cameraService; bool _isRecording = false; String? _lastCapturedPath; FlashMode _flashMode = FlashMode.off; CameraProvider({required CameraService cameraService}) : _cameraService = cameraService; CameraController? get controller => _cameraService.controller; bool get isRecording => _isRecording; String? get lastCapturedPath => _lastCapturedPath; FlashMode get flashMode => _flashMode; Future<void> initialize() async { await _cameraService.initialize(); notifyListeners(); } Future<void> switchCamera() async { await _cameraService.switchCamera(); notifyListeners(); } Future<void> setFlashMode(FlashMode mode) async { await _cameraService.setFlashMode(mode); _flashMode = mode; notifyListeners(); } Future<void> takePicture() async { final path = await _cameraService.takePicture(); if (path != null) { _lastCapturedPath = path; notifyListeners(); } } Future<void> startRecording() async { final error = await _cameraService.startVideoRecording(); if (error == null) { _isRecording = true; notifyListeners(); } } Future<void> stopRecording() async { final path = await _cameraService.stopVideoRecording(); _isRecording = false; if (path != null) { _lastCapturedPath = path; } notifyListeners(); } @override void dispose() { _cameraService.dispose(); super.dispose(); } }
-
Create Camera Widgets
class CameraPreviewWidget extends StatelessWidget { final CameraController controller; final bool isRecording; const CameraPreviewWidget({ required this.controller, this.isRecording = false, }); @override Widget build(BuildContext context) { return Stack( children: [ CameraPreview(controller), if (isRecording) Positioned( top: 16, right: 16, child: Container( padding: EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.red.withOpacity(0.8), borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Icon(Icons.fiber_manual_record, color: Colors.white), SizedBox(width: 8), Text( 'Recording', style: TextStyle(color: Colors.white), ), ], ), ), ), ], ); } } class CameraControls extends StatelessWidget { final VoidCallback onCapture; final VoidCallback onSwitchCamera; final Function(FlashMode) onFlashModeChanged; final bool isRecording; final VoidCallback onRecordStart; final VoidCallback onRecordStop; const CameraControls({ required this.onCapture, required this.onSwitchCamera, required this.onFlashModeChanged, required this.isRecording, required this.onRecordStart, required this.onRecordStop, }); @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ IconButton( icon: Icon(Icons.flash_auto), onPressed: () { onFlashModeChanged(FlashMode.auto); }, ), IconButton( icon: Icon( isRecording ? Icons.stop : Icons.fiber_manual_record, color: isRecording ? Colors.red : Colors.white, size: 48, ), onPressed: isRecording ? onRecordStop : onRecordStart, ), IconButton( icon: Icon(Icons.camera), onPressed: onCapture, ), IconButton( icon: Icon(Icons.flip_camera_ios), onPressed: onSwitchCamera, ), ], ), ); } }
-
Create Main Screen
class CameraScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Consumer<CameraProvider>( builder: (context, provider, child) { if (provider.controller == null) { return Center( child: CircularProgressIndicator(), ); } return Stack( children: [ CameraPreviewWidget( controller: provider.controller!, isRecording: provider.isRecording, ), Positioned( bottom: 0, left: 0, right: 0, child: CameraControls( onCapture: () => provider.takePicture(), onSwitchCamera: () => provider.switchCamera(), onFlashModeChanged: (mode) => provider.setFlashMode(mode), isRecording: provider.isRecording, onRecordStart: () => provider.startRecording(), onRecordStop: () => provider.stopRecording(), ), ), ], ); }, ), ); } }
Best Practices
-
Performance
- Handle camera lifecycle
- Manage resources
- Optimize preview
- Handle orientation
-
User Experience
- Show loading states
- Provide feedback
- Handle errors
- Support gestures
-
Security
- Request permissions
- Handle sensitive data
- Secure storage
- Validate input
-
Testing
- Test permissions
- Verify capture
- Check recording
- Test edge cases
Conclusion
This tutorial has shown you how to implement camera functionality in Flutter with features like:
- Photo capture
- Video recording
- Camera switching
- Flash control
- Permission handling
You can extend this implementation by adding:
- Image filters
- Video trimming
- Slow motion
- Time-lapse
- AR features
Remember to:
- Handle permissions properly
- Test on multiple devices
- Consider performance
- Follow platform guidelines
- Keep dependencies updated
This implementation provides a solid foundation for adding camera functionality to your Flutter app.