camerawesome-flutter
Skill8 starsChanged 48 days agoMITView on GitHub
Use when integrating, configuring, optimizing, debugging, or migrating camera and video recording experiences in Flutter using the camerawesome package. Covers CameraAwesomeBuilder modes (.awesome, .custom, .previewOnly, .analysisOnly), CameraState lifecycle transitions, Android CameraX and iOS AVFoundation native setup, runtime permissions, concurrent sensor configurations, image analysis with ML Kit, orientation-aware controls, and thermal/memory performance tuning. Do not use for camera plugin from Flutter team (flutter/packages), simple photo picking without live preview (use image_picker), or desktop/web camera support.
In this repository70 other files it gives its agents.
- dart-args-production-cliSkill
- dart-flutter-equatable-engineeringSkill
- dart-flutter-ffi-systems-engineeringSkill
- dart-metaSkill
- document-camera-frameSkill
- document-scan-flutterSkill
- figma-to-flutter-expertSkill
- flutter-animations-production-engineeringSkill
- flutter-app-hardening-reverse-engineeringSkill
- flutter-app-icon-engineeringSkill
- flutter-application-security-engineeringSkill
- flutter-app-links-deep-link-engineeringSkill
- flutter-cachingSkill
- flutter-concurrency-memory-performanceSkill
- flutter-confetti-engineeringSkill
- flutter-dart-package-engineeringSkill
- flutter-design-system-from-design-mdSkill
- flutter-dio-cache-interceptor-engineeringSkill
- flutter-dio-engineeringSkill
- flutter-doc-scannerSkill
- flutter-drift-persistence-engineeringSkill
- flutter-expert-tipsSkill
- flutter-firebase-analyticsSkill
- flutter-firebase-core-engineeringSkill
- flutter-firebase-messagingSkill
- flutter-get-it-injectable-dependency-injectionSkill
- flutter-go-router-engineeringSkill
- flutter-hive-persistence-engineeringSkill
- flutter-image-performanceSkill
- flutter-image-picker-production-engineeringSkill
- flutter-lints-static-analysis-engineeringSkill
- flutter-local-notifications-production-engineeringSkill
- flutter-memory-leak-engineeringSkill
- flutter-mobile-developmentSkill
- flutter-motion-engineeringSkill
- flutter-multi-environmentSkill
- flutter-native-splash-engineeringSkill
- flutter-package-plugin-selectionSkill
- flutter-path-provider-production-storageSkill
- flutter-performance-jank-optimizationSkill
- flutter-permission-handler-engineeringSkill
- flutter-pinputSkill
- flutter-production-build-releaseSkill
- flutter-production-engineeringSkill
- flutter-production-loggingSkill
- flutter-pro-image-editor-engineeringSkill
- flutter-secure-storage-security-engineeringSkill
- flutter-sentry-engineeringSkill
- flutter-share-plus-engineeringSkill
- flutter-skeletonizer-engineeringSkill
- flutter-squadron-concurrency-engineeringSkill
- flutter-supabase-engineeringSkill
- flutter-svg-engineeringSkill
- flutter-ui-performance-engineeringSkill
- flutter-url-launcher-engineeringSkill
- flutter-video-player-production-engineeringSkill
- flutter-webview-production-engineeringSkill
- freezed-production-engineeringSkill
- html-to-flutterSkill
- hydrated-blocSkill
- The first 60 shown.
The file
Show its rules and text
We split this skill into the rules it gives an agent
40 rules. About 0 are common, 9 are distinctive, and 31 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈9
Distinctive · estimate
This author's own choices. Worth a look.
31
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 56
Verify camerawesome version.
- Very specific: line 61
Check android/app/build.gradle: ensure minSdkVersion is at least 21 (recommended: 24+ for modern CameraX features) and compileSdkVersion is …
- Very specific: line 62
Check android/app/src/main/AndroidManifest.xml: inspect declared permissions (RECORD_AUDIO, ACCESS_FINE_LOCATION).
- Very specific: line 64
Check ios/Runner/Info.plist: confirm NSCameraUsageDescription is declared.
- Very specific: line 65
Check ios/Podfile: check deployment target (must be 12.0 or higher).
- Very specific: line 73
Add the dependency to pubspec.yaml:
- Distinctive: line 80
Import the unified library in your Dart code:
- Very specific: line 94
Ensure minSdkVersion is at least 21:
- Distinctive: line 110
Optional permissions: must be declared manually if utilized:
- Very specific: line 123
If other plugins (such as Google Play Services or Firebase) create classpath or interface conflicts, override dependencies in root …
- Very specific: line 281
When switching between photo and video, state.setState(CaptureMode.video) transitions PhotoCameraState -> VideoCameraState.
- Very specific: line 282
When starting video recording, videoState.startRecording() transitions VideoCameraState -> VideoRecordingCameraState.
- Very specific: line 286
AppLifecycleState.inactive/paused/detached: If video is actively recording, CamerAwesome automatically calls stopRecording(). - Distinctive: line 290
Critical Rule: Only one native camera session can be active at a time (P).
- Very specific: line 291
When navigating to another screen, if the camera screen remains in the navigation stack without unmounting (e.g.
- Distinctive: line 292
If pushing a subroute from a camera screen:
- Very specific: line 304
If popping or replacing routes, ensure CameraAwesomeBuilder is completely unmounted before instantiating a new camera screen.
- Distinctive: line 329
When a user permanently denies camera or microphone permissions:
- Very specific: line 389
Use CaptureRequestBuilder to generate structured paths:
- Very specific: line 678
Critical Rule: Always await your image processing logic in onImageForAnalysis.
- Very specific: line 681
Create a dedicated adapter extension to convert AnalysisImage into ML Kit's InputImage:
- Distinctive: line 732
When ML Kit detects bounding boxes or facial contours, the coordinates are in analysis image space, which differs from Flutter screen …
- Very specific: line 734
Use AnalysisPreview.convertFromImage(...) to map detected points accurately (D):
- Distinctive: line 753
Check compatibility dynamically before configuring simultaneous capture (D):
- Very specific: line 768
When building a custom camera UI with CameraAwesomeBuilder.custom(...), avoid unnecessary widget rebuilds:
- Very specific: line 770
Do NOT rebuild the entire screen on property updates: Keep CameraAwesomeBuilder at the top of the subtree and isolate dynamic buttons with …
- Very specific: line 843
DO bound the image analysis frame rate: Always provide maxFramesPerSecond (e.g.
- Very specific: line 846
DO keep
CameraAwesomeBuilderhigh in the tree: Never trigger a rebuild of the widget owning CameraAwesomeBuilder merely because zoom … - Very specific: line 857
State Transition Collisions: Never call state.setState() while state is PreparingCameraState (throws CameraNotReadyException).
- Very specific: line 858
Video Recording State Invariant: When video recording is started, you must call recordingState.stopRecording() before changing capture mode …
- Very specific: line 859
Disposed Subscriptions: When listening to sensorConfig.zoom$ or sensorConfig.flashMode$ in StatefulWidget classes, always cancel the …
- Very specific: line 868
Use CameraAwesomeBuilder.awesome directly within a standard StatefulWidget.
- Distinctive: line 870
No separate service or state management layer required.
- Very specific: line 873
Use CameraAwesomeBuilder.custom with a dedicated presentation widget.
- Very specific: line 908
App Store Submission Requirement: If microphone is not utilized, strip the AVAudioSession microphone reference via Podfile …
- Very specific: line 920
When widget testing screens containing CameraAwesomeBuilder, mock or wrap the camera builder behind a testable abstraction, or test …
- Distinctive: line 1023
When tasked with implementing or modifying CamerAwesome code in a Flutter project, follow this exact operational sequence:
- Very specific: line 1454
DO verify the installed camerawesome version before writing code.
- Very specific: line 1459
DO use preview.convertFromImage(...) to map detected ML points into Flutter screen coordinates.
- Very specific: line 1464
DON'T: call state.setState() while the camera is in PreparingCameraState.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
CamerAwesome for Flutter
Purpose and evidence policy
Engineer reliable, battery-efficient, high-framerate camera interfaces and real-time computer vision pipelines using camerawesome. CamerAwesome wraps Google's Android Jetpack CameraX and Apple's AVFoundation behind a declarative Flutter API with reactive state streams.
Evidence labels used throughout this skill:
- D — Documented package or platform behavior verified in official documentation or source code.
- P — Platform-specific limitation or native requirement (Android CameraX / iOS AVFoundation).
- E — Engineering recommendation, architectural inference, or operational best practice.
Research baseline
- Researched package:
camerawesome - Verified version:
2.5.0(Latest pub.dev release) - Dart SDK constraint:
>=3.2.6 <4.0.0 - Flutter constraint:
>=1.10.0(Modern production apps target Flutter 3.22+) - Supported platforms: Android (minSdkVersion 21+, CameraX 1.4.2) and iOS (iOS 12.0+, AVFoundation) exclusively (D).
- Unsupported platforms: Web, macOS, Windows, Linux (D). Never configure or promise CamerAwesome on desktop or web targets.
- Primary sources:
- Official documentation:
https://docs.page/Apparence-io/camera_awesome - GitHub repository & source code:
https://github.com/Apparence-io/camera_awesome - Package pub.dev:
https://pub.dev/packages/camerawesome
- Official documentation:
When to use CamerAwesome
Optimal use cases
- In-app camera preview with custom UI overlays (shutter controls, filter carousels, custom reticles).
- QR code, barcode scanning, document boundary detection, or face tracking with simultaneous live preview using Google ML Kit.
- Turn-key camera experiences requiring built-in Material/Cupertino controls, filters, zoom indicators, and orientation handling with minimal boilerplate (
CameraAwesomeBuilder.awesome). - High-efficiency video recording with configurable bitrates, fallback quality strategies, and audio toggling.
- Dual/multi-camera capture (Beta) on supported multi-lens devices.
When NOT to use CamerAwesome
- Simple image or video selection from the system gallery or native system camera app — use
image_pickerinstead (E). - Web, macOS, Windows, or Linux desktop camera requirements — CamerAwesome does not support desktop or browser engines (D).
- Ultra-low-latency raw frame rendering pipelines requiring customized native C++/Vulkan/Metal shaders — implement custom Flutter Platform Views or FFI bindings (E).
Before editing a project
Inspect the target Flutter project thoroughly before introducing or refactoring CamerAwesome code:
- Check
pubspec.yaml:- Verify
camerawesomeversion. If migrating from1.x, note thatCameraLayoutBuildersignature andAnalysisConfigchanged fundamentally in2.x. - Inspect existing camera dependencies (
camera,image_picker,google_mlkit_*) to avoid duplicate native camera sessions or conflicting Gradle dependencies.
- Verify
- Check target platforms:
- Confirm the project targets only Android and/or iOS. If multi-platform (web/desktop), isolate CamerAwesome behind a platform-agnostic service interface with runtime conditional loading.
- Inspect Android configuration:
- Check
android/app/build.gradle: ensureminSdkVersionis at least 21 (recommended: 24+ for modern CameraX features) andcompileSdkVersionis 33+. - Check
android/app/src/main/AndroidManifest.xml: inspect declared permissions (RECORD_AUDIO,ACCESS_FINE_LOCATION).
- Check
- Inspect iOS configuration:
- Check
ios/Runner/Info.plist: confirmNSCameraUsageDescriptionis declared. Confirm whetherNSMicrophoneUsageDescriptionis needed or should be stripped. - Check
ios/Podfile: check deployment target (must be12.0or higher).
- Check
- Inspect route & state architecture:
- Identify where the camera screen sits in the navigation hierarchy. Determine whether pushing/popping routes could cause overlapping camera widget lifecycles.
Installation
Add the dependency to pubspec.yaml:
dependencies:
camerawesome: ^2.5.0Import the unified library in your Dart code:
import 'package:camerawesome/camerawesome_plugin.dart';
import 'package:camerawesome/pigeon.dart';Platform configuration
Android setup
1. SDK version (android/app/build.gradle)
Ensure minSdkVersion is at least 21:
android {
compileSdkVersion 34
defaultConfig {
minSdkVersion 21
targetSdkVersion 34
}
}2. Permissions (android/app/src/main/AndroidManifest.xml)
Camera permission (android.permission.CAMERA) is automatically injected by the library's AndroidManifest.xml.
Optional permissions must be declared manually if utilized:
- Audio recording (required for video recording with sound):
<uses-permission android:name="android.permission.RECORD_AUDIO" /> - GPS location in Exif metadata:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> - Storage: Starting from version
1.4.0,WRITE_EXTERNAL_STORAGEis not included. CamerAwesome saves files to app-specific directories (path_provider). Do not declare legacy storage permissions unless supporting Android 9 (API 28) or lower with shared external storage (D).
3. Resolving Gradle dependency conflicts
If other plugins (such as Google Play Services or Firebase) create classpath or interface conflicts, override dependencies in root android/build.gradle:
buildscript {
ext {
compileSdkVersion = 34
minSdkVersion = 21
playServicesLocationVersion = "21.3.0"
exifInterfaceVersion = "1.4.0"
}
}iOS setup
1. Minimum deployment target (ios/Podfile)
AVFoundation modern pipelines require iOS 12.0+:
platform :ios, '12.0'2. Privacy usage descriptions (ios/Runner/Info.plist)
Declare mandatory usage descriptions:
<key>NSCameraUsageDescription</key>
<string>This app requires camera access to capture photos and scan barcodes.</string>
<!-- Include ONLY if recording video with audio -->
<key>NSMicrophoneUsageDescription</key>
<string>This app requires microphone access to record audio in videos.</string>
<!-- Include ONLY if saving GPS coordinates to image Exif -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app requires location access to geotag captured photos.</string>3. Preview-only / Barcode-only App Store rejection fix
P: Apple's automated App Store review checks for AVAudioSession microphone symbols. If your app only uses the camera preview (e.g. for barcode scanning or face detection) and does not declare NSMicrophoneUsageDescription, Apple will reject the binary.
Strip microphone API references in ios/Podfile (D):
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
'AUDIO_SESSION_MICROPHONE=0'
]
end
end
endCore mental model
CamerAwesome is built around an asynchronous State Pattern and reactive RxDart streams:
[ PreparingCameraState ] (Camera initialization & permission checks)
│
├───> [ PhotoCameraState ] ──(takePhoto)──> CaptureRequest & MediaCapture
│
├───> [ VideoCameraState ] ──(startRecording)──> [ VideoRecordingCameraState ]
│ │
│ (pause/resume/stop) ◄──┘
│
├───> [ PreviewCameraState ] (Barcode, QR, or preview-only)
│
└───> [ AnalysisCameraState ] (AI/headless analysis without preview)Key classes and their responsibilities
CameraAwesomeBuilder: The top-levelStatefulWidgetcoordinating native camera lifecycle, texture attachment, orientation listeners, and widget layout.CameraState: The base abstract state object passed to builders. Exposes sensor controls, filter selectors, media capture streams, and the polymorphicstate.when(...)dispatcher.SensorConfig: Reactive configuration for a physical lens or multi-camera group. Exposes streams and setters forzoom$,flashMode$,aspectRatio$, andbrightness$.AnalysisPreview: Provided to layout builders in 2.x. Encapsulates preview dimensions, native scaling factor, and coordinate translation functions (convertFromImage,convertPoint).SaveConfig: Defines persistence destinations (photoPathBuilder,videoPathBuilder), recording parameters (videoOptions), Exif preferences, and front camera mirroring.MediaCapture: Emitted bycaptureState$stream with status (capturing,success,failure), media type (isPicture,isVideo), and reference to outputXFile.
Choosing the correct camera API
CamerAwesome provides four dedicated factory constructors on CameraAwesomeBuilder. Select strictly based on the application requirement:
| Constructor | Preview displayed | Photo/Video capture | Image analysis stream | Ideal use case |
CameraAwesomeBuilder.awesome(...) | Yes | Yes | Optional | Full-featured turn-key camera with default buttons, filters, and timer. |
CameraAwesomeBuilder.custom(...) | Yes | Yes | Optional | Production custom camera app with bespoke layout, overlays, and design system. |
CameraAwesomeBuilder.previewOnly(...) | Yes | No | Yes | QR/barcode scanners, face detection with live preview, AR reticles. |
CameraAwesomeBuilder.analysisOnly(...) | No | No | Yes | Image filtering, custom canvas drawing, background CV pipelines without native preview texture. |
Minimal production-ready setup
A clean, self-contained implementation with standard capture, thumbnail tap handling, and file persistence:
import 'package:camerawesome/camerawesome_plugin.dart';
import 'package:camerawesome/pigeon.dart';
import 'package:flutter/material.dart';
class SimpleCameraScreen extends StatelessWidget {
const SimpleCameraScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: CameraAwesomeBuilder.awesome(
saveConfig: SaveConfig.photoAndVideo(
initialCaptureMode: CaptureMode.photo,
mirrorFrontCamera: true,
),
sensorConfig: SensorConfig.single(
sensor: Sensor.position(SensorPosition.back),
flashMode: FlashMode.auto,
aspectRatio: CameraAspectRatios.ratio_4_3,
),
previewFit: CameraPreviewFit.contain,
enablePhysicalButton: true,
onMediaTap: (MediaCapture mediaCapture) {
mediaCapture.captureRequest.when(
single: (single) {
debugPrint('Captured file: ${single.file?.path}');
},
multiple: (multiple) {
for (final entry in multiple.fileBySensor.entries) {
debugPrint('Captured sensor ${entry.key.position}: ${entry.value?.path}');
}
},
);
},
),
);
}
}Camera state and lifecycle
State lifecycle transitions
When CameraAwesomeBuilder mounts:
- It seeds
PreparingCameraState. - Native camera permissions are verified and requested.
- Once permissions are granted, native CameraX / AVFoundation initializes the lens and streaming pipeline.
PreparingCameraStatetransitions to the targetCameraState(PhotoCameraState,VideoCameraState,PreviewCameraState, orAnalysisCameraState).- When switching between photo and video,
state.setState(CaptureMode.video)transitionsPhotoCameraState->VideoCameraState. - When starting video recording,
videoState.startRecording()transitionsVideoCameraState->VideoRecordingCameraState. Stopping returns toVideoCameraState.
App backgrounding and foregrounding
CameraAwesomeBuilder registers a WidgetsBindingObserver internally (D):
AppLifecycleState.inactive/paused/detached: If video is actively recording, CamerAwesome automatically callsstopRecording(). Native streaming stops.AppLifecycleState.resumed: On Android, CamerAwesome automatically resets zoom to 1.0x optical (setZoomToOneX()).
Navigation and route change rules
Critical Rule: Only one native camera session can be active at a time (P).
- When navigating to another screen, if the camera screen remains in the navigation stack without unmounting (e.g.
Navigator.push), native CameraX/AVFoundation continues streaming unless explicitly paused or stopped (E). - If pushing a subroute from a camera screen:
// Stop analysis before navigating to conserve CPU/battery cameraState.analysisController?.stop(); await Navigator.of(context).push( MaterialPageRoute(builder: (_) => const MediaPreviewScreen()), ); // Restart analysis upon return cameraState.analysisController?.start(); - If popping or replacing routes, ensure
CameraAwesomeBuilderis completely unmounted before instantiating a new camera screen. Do not run parallel route transitions that mount two camera screens simultaneously.
Permissions
Permission architecture
CamerAwesome checks and requests permissions in PreparingCameraState via CamerawesomePlugin.checkAndRequestPermissions(...) (D).
// To receive permission outcomes directly in your layout:
CameraAwesomeBuilder.awesome(
saveConfig: SaveConfig.photo(),
onMediaCaptureEvent: (event) {
if (event.status == MediaCaptureStatus.failure) {
debugPrint('Capture error: ${event.exception}');
}
},
progressIndicator: const Center(
child: CircularProgressIndicator.adaptive(),
),
);Handling permanent denial
When a user permanently denies camera or microphone permissions:
- Native permission dialogs will not reappear.
- The camera will remain in
PreparingCameraStateand display theprogressIndicator. - To provide a production-grade UX, perform a pre-check with
permission_handlerbefore navigating to the camera screen, displaying a clear "Permission Denied" view with a button directing to system app settings:
import 'package:permission_handler/permission_handler.dart';
Future<bool> ensureCameraPermissions(BuildContext context) async {
final status = await Permission.camera.request();
if (status.isPermanentlyDenied) {
if (context.mounted) {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Camera Access Required'),
content: const Text('Please enable camera permissions in settings to take photos.'),
actions: [
TextButton(
onPressed: () => openAppSettings(),
child: const Text('Open Settings'),
),
],
),
);
}
return false;
}
return status.isGranted;
}Photo capture
Recommended implementation pattern
In custom layouts, trigger photos through PhotoCameraState:
Future<void> capturePhoto(PhotoCameraState photoState) async {
try {
final CaptureRequest request = await photoState.takePhoto(
onPhoto: (CaptureRequest req) {
debugPrint('Photo saved successfully to: ${req.path}');
},
onPhotoFailed: (Exception exception) {
debugPrint('Failed to save photo: $exception');
},
);
debugPrint('Capture initiated for path: ${request.path}');
} on CameraNotReadyException catch (e) {
debugPrint('Camera is still preparing: $e');
} catch (e) {
debugPrint('Unexpected error during capture: $e');
}
}Output path customization
Use CaptureRequestBuilder to generate structured paths:
SaveConfig.photo(
pathBuilder: (List<Sensor> sensors) async {
final Directory extDir = await getApplicationDocumentsDirectory();
final String dirPath = '${extDir.path}/app_photos';
await Directory(dirPath).create(recursive: true);
final String filePath = '$dirPath/${DateTime.now().millisecondsSinceEpoch}.jpg';
return SingleCaptureRequest(filePath, sensors.first);
},
exifPreferences: ExifPreferences(saveGPSLocation: false),
mirrorFrontCamera: true,
)Video capture
Recommended implementation pattern
Video recording requires transitioning between VideoCameraState and VideoRecordingCameraState:
Widget buildVideoShutterButton(CameraState state) {
return state.when(
onVideoMode: (VideoCameraState videoState) {
return IconButton(
icon: const Icon(Icons.fiber_manual_record, color: Colors.red, size: 64),
onPressed: () async {
// Starts recording; triggers transition to VideoRecordingCameraState
await videoState.startRecording();
},
);
},
onVideoRecordingMode: (VideoRecordingCameraState recordingState) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
// Pause / Resume toggle
StreamBuilder<MediaCapture?>(
stream: recordingState.captureState$,
builder: (context, snapshot) {
final capture = snapshot.data;
final isPaused = capture?.videoState == VideoState.paused;
return IconButton(
icon: Icon(isPaused ? Icons.play_arrow : Icons.pause, color: Colors.white),
onPressed: () {
if (capture != null) {
if (isPaused) {
recordingState.resumeRecording(capture);
} else {
recordingState.pauseRecording(capture);
}
}
},
);
},
),
// Stop button
IconButton(
icon: const Icon(Icons.stop, color: Colors.red, size: 64),
onPressed: () async {
await recordingState.stopRecording(
onVideo: (CaptureRequest req) {
debugPrint('Video recorded to: ${req.path}');
},
onVideoFailed: (Exception err) {
debugPrint('Video recording failed: $err');
},
);
},
),
],
);
},
) ?? const SizedBox.shrink();
}Video configuration parameters
Configure bitrate, resolution quality, and fallback rules via VideoOptions:
SaveConfig.video(
videoOptions: VideoOptions(
enableAudio: true,
quality: VideoRecordingQuality.fhd, // 1080p
android: AndroidVideoOptions(
bitrate: 6000000, // 6 Mbps
fallbackStrategy: QualityFallbackStrategy.lower,
),
ios: CupertinoVideoOptions(
fps: 30,
codec: CupertinoVideoCodec.h264,
fileType: CupertinoFileType.quickTimeMovie,
),
),
mirrorFrontCamera: true,
)Camera switching and sensors
Switching front and back lenses
Switching lenses is supported across all camera states:
// Switch between front and back
await cameraState.switchCameraSensor(
aspectRatio: CameraAspectRatios.ratio_4_3,
zoom: 0.0,
flash: FlashMode.none,
);Physical sensor types (iOS)
On iOS devices equipped with multiple lenses (e.g. Ultra-Wide, Wide, Telephoto), CamerAwesome exposes lens selection:
SensorType.wideAngle(Standard lens)SensorType.ultraWideAngle(0.5x wide lens)SensorType.telephoto(2x/3x optical zoom)SensorType.trueDepth(Front-facing depth camera)
P: Android does not currently expose physical sensor switching through CamerAwesome's SensorType selector. Android CameraX automatically selects the logical multicam unit.
Dual / Multi-camera (BETA)
camerawesome supports simultaneous front/back streaming in Beta:
SensorConfig.multiple(
sensors: [
Sensor.position(SensorPosition.back),
Sensor.position(SensorPosition.front),
],
)CAUTION: Simultaneous multi-camera streaming requires hardware level Camera2 LEVEL_3 or multi-camera support on Android, and iPhone XR/XS or newer on iOS (P). Do not enable multi-camera without runtime hardware capability checks.
Focus, zoom, exposure, and flash
Zoom control
Zoom ranges between 0.0 (minimum/1.0x optical) and 1.0 (maximum digital zoom):
// Programmatic zoom
await cameraState.sensorConfig.setZoom(0.5);
// Reactive zoom stream
StreamBuilder<double>(
stream: cameraState.sensorConfig.zoom$,
builder: (context, snapshot) {
final double zoom = snapshot.data ?? 0.0;
return Text('${(zoom * 10).toStringAsFixed(1)}x');
},
);Flash modes
Cycle or set flash modes (FlashMode.none, FlashMode.auto, FlashMode.on, FlashMode.always):
// Cycle to next mode
cameraState.sensorConfig.switchCameraFlash();
// Explicit set
await cameraState.sensorConfig.setFlashMode(FlashMode.auto);Tap to focus & exposure
In custom builders, implement tap-to-focus using onPreviewTapBuilder:
CameraAwesomeBuilder.custom(
onPreviewTapBuilder: (state) => OnPreviewTap(
onTap: (Offset position, PreviewSize flutterPreviewSize, PreviewSize pixelPreviewSize) {
state.when(
onPhotoMode: (s) => s.focusOnPoint(
flutterPosition: position,
flutterPreviewSize: flutterPreviewSize,
pixelPreviewSize: pixelPreviewSize,
),
onVideoMode: (s) => s.focusOnPoint(
flutterPosition: position,
flutterPreviewSize: flutterPreviewSize,
pixelPreviewSize: pixelPreviewSize,
),
);
},
),
builder: (state, preview) => /* ... */,
saveConfig: SaveConfig.photo(),
)Orientation and aspect ratio
Native orientation behavior
D: CameraAwesomeBuilder enforces portrait lock on widget dependencies (SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])).
The camera preview texture itself does not rotate. Instead, device orientation changes are broadcast natively via CamerawesomePlugin.getNativeOrientation().
Orientation-aware UI with AwesomeOrientedWidget
Wrap controls and action overlays in AwesomeOrientedWidget to smoothly counter-rotate icons when the user holds the phone in landscape:
AwesomeOrientedWidget(
rotateWithDevice: true,
child: IconButton(
icon: const Icon(Icons.flash_on),
onPressed: () => cameraState.sensorConfig.switchCameraFlash(),
),
)Aspect ratio configuration
Choose between:
CameraAspectRatios.ratio_16_9CameraAspectRatios.ratio_4_3(Default standard for photographic sensors)CameraAspectRatios.ratio_1_1(Clipped square preview)
// Switch ratio programmatically
await cameraState.sensorConfig.setAspectRatio(CameraAspectRatios.ratio_16_9);Save configuration and media output
SaveConfig parameters and builders
SaveConfig controls how images and videos are written:
final saveConfig = SaveConfig.photoAndVideo(
initialCaptureMode: CaptureMode.photo,
mirrorFrontCamera: true,
photoPathBuilder: (List<Sensor> sensors) async {
final dir = await getApplicationDocumentsDirectory();
final path = '${dir.path}/photos/photo_${DateTime.now().millisecondsSinceEpoch}.jpg';
return SingleCaptureRequest(path, sensors.first);
},
videoPathBuilder: (List<Sensor> sensors) async {
final dir = await getApplicationDocumentsDirectory();
final path = '${dir.path}/videos/video_${DateTime.now().millisecondsSinceEpoch}.mp4';
return SingleCaptureRequest(path, sensors.first);
},
exifPreferences: ExifPreferences(saveGPSLocation: false),
);Image analysis
CamerAwesome provides a high-performance native image analysis pipeline designed for real-time computer vision (QR/barcodes, face detection, OCR).
Native streaming differences
- Android: Powered by CameraX
ImageAnalysisuse case. Analysis runs independently of the preview stream and can run at a different resolution (e.g. 500px wide) (P). - iOS: Analysis frames and preview frames originate from the same
AVCaptureVideoDataOutput. Setting analysis reduces the preview capture resolution to prevent frame drops (P).
Analysis configuration
Configure analysis parameters via AnalysisConfig:
AnalysisConfig(
// Target width for Android CameraX (500 is optimal for barcodes/faces)
androidOptions: const AndroidAnalysisOptions.nv21(width: 500),
// iOS only supports bgra8888
cupertinoOptions: const CupertinoAnalysisOptions.bgra8888(),
// Throttle frame delivery (crucial for CPU/battery savings!)
maxFramesPerSecond: 10,
autoStart: true,
)Backpressure and the native handshake
D: CamerAwesome implements an explicit acknowledgement handshake in AnalysisController:
imageSubscription = _images$?.listen((event) async {
await onImageListener!(AnalysisImage.from(event));
await CamerawesomePlugin.receivedImageFromStream();
});CamerawesomePlugin.receivedImageFromStream()notifies native CameraX/AVFoundation that the Dart layer has finished processing the frame.- Critical Rule: Always
awaityour image processing logic inonImageForAnalysis. If you launch un-awaited asynchronous futures, frames will continue to arrive faster than your model can infer, resulting in memory growth and UI isolate stutter (E).
Integrating with Google ML Kit
Create a dedicated adapter extension to convert AnalysisImage into ML Kit's InputImage:
import 'dart:io';
import 'package:camerawesome/camerawesome_plugin.dart';
import 'package:google_mlkit_commons/google_mlkit_commons.dart';
extension MLKitImageAnalysisAdapter on AnalysisImage {
InputImage toInputImage() {
return when(
nv21: (Nv21Image image) {
return InputImage.fromBytes(
bytes: image.bytes,
metadata: InputImageMetadata(
size: image.size,
rotation: _mlKitRotation(rotation),
format: InputImageFormat.nv21,
bytesPerRow: image.planes.first.bytesPerRow,
),
);
},
bgra8888: (Bgra8888Image image) {
return InputImage.fromBytes(
bytes: image.bytes,
metadata: InputImageMetadata(
size: image.size,
rotation: _mlKitRotation(rotation),
format: InputImageFormat.bgra8888,
bytesPerRow: image.planes.first.bytesPerRow,
),
);
},
)!;
}
InputImageRotation _mlKitRotation(InputAnalysisImageRotation rot) {
switch (rot) {
case InputAnalysisImageRotation.rotation0deg:
return InputImageRotation.rotation0deg;
case InputAnalysisImageRotation.rotation90deg:
return InputImageRotation.rotation90deg;
case InputAnalysisImageRotation.rotation180deg:
return InputImageRotation.rotation180deg;
case InputAnalysisImageRotation.rotation270deg:
return InputImageRotation.rotation270deg;
}
}
}Coordinate mapping: Analysis coordinates to preview coordinates
When ML Kit detects bounding boxes or facial contours, the coordinates are in analysis image space, which differs from Flutter screen coordinates due to aspect ratio cropping and orientation.
Use AnalysisPreview.convertFromImage(...) to map detected points accurately (D):
// Map corner points from barcode detection to Flutter preview pixels:
final Offset topLeftFlutter = preview.convertFromImage(
Offset(barcode.cornerPoints[0].x.toDouble(), barcode.cornerPoints[0].y.toDouble()),
analysisImage,
);
final Offset bottomRightFlutter = preview.convertFromImage(
Offset(barcode.cornerPoints[2].x.toDouble(), barcode.cornerPoints[2].y.toDouble()),
analysisImage,
);
final Rect boundingBoxOnScreen = Rect.fromPoints(topLeftFlutter, bottomRightFlutter);Android simultaneous recording and analysis check
P: On Android, recording video and streaming image analysis simultaneously is only supported on hardware level LEVEL_3 devices.
Check compatibility dynamically before configuring simultaneous capture (D):
final bool isSupported = await CameraCharacteristics
.isVideoRecordingAndImageAnalysisSupported(SensorPosition.back);
if (!isSupported) {
// Disable image analysis while recording, or run preview-only mode
}Custom camera UI
When building a custom camera UI with CameraAwesomeBuilder.custom(...), avoid unnecessary widget rebuilds:
- Do NOT rebuild the entire screen on property updates: Keep
CameraAwesomeBuilderat the top of the subtree and isolate dynamic buttons with targetedStreamBuilderwidgets listening tosensorConfig.flashMode$,sensorConfig.zoom$, etc. - Access state via
builder: (CameraState state, AnalysisPreview preview): The builder gives direct access to the state machine.
CameraAwesomeBuilder.custom(
saveConfig: SaveConfig.photo(),
builder: (CameraState state, AnalysisPreview preview) {
return Stack(
children: [
// Reticle / Bounding box overlay
Positioned.fill(
child: CustomPaint(
painter: ReticlePainter(previewRect: preview.rect),
),
),
// Bottom Shutter Controls
Positioned(
bottom: 40,
left: 0,
right: 0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// Flash Toggle Button (rebuilds only itself)
StreamBuilder<FlashMode>(
stream: state.sensorConfig.flashMode$,
builder: (context, snapshot) {
final flash = snapshot.data ?? FlashMode.none;
return IconButton(
icon: Icon(
flash == FlashMode.none ? Icons.flash_off : Icons.flash_on,
color: Colors.white,
),
onPressed: () => state.sensorConfig.switchCameraFlash(),
);
},
),
// Shutter Button
GestureDetector(
onTap: () {
state.when(
onPhotoMode: (photoState) => photoState.takePhoto(),
);
},
child: Container(
width: 72,
height: 72,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 4),
),
),
),
// Lens Switcher
IconButton(
icon: const Icon(Icons.flip_camera_ios, color: Colors.white),
onPressed: () => state.switchCameraSensor(),
),
],
),
),
],
);
},
)Performance engineering
Concrete DO and DON'T rules
- DO bound the image analysis frame rate: Always provide
maxFramesPerSecond(e.g. 5 to 15 FPS) inAnalysisConfig. An unconstrained stream sends 30 to 60 high-resolution byte buffers per second across the platform channel, saturating the Flutter isolate and generating excessive GC pressure (E). - DO use small resolutions for computer vision: Set
width: 250or500inAndroidAnalysisOptions.nv21. Barcode and face detection models perform identically on 500px images while reducing native-to-Dart memory transfer by up to 80% (E). - DO stop analysis when results are obtained: Once a valid QR code or barcode is scanned, call
cameraState.analysisController?.stop()immediately to prevent wasteful background compute (E). - DO keep
CameraAwesomeBuilderhigh in the tree: Never trigger a rebuild of the widget owningCameraAwesomeBuildermerely because zoom level or flash mode changed (E). - DON'T perform heavy synchronous ML or image processing on the UI isolate: If converting large raw YUV/NV21 byte arrays to JPEG using package
image, offload the computation usingcompute()or a background Dart isolate (E). - DON'T unmount and remount
CameraAwesomeBuilderto change settings: Usestate.switchCameraSensor(),state.sensorConfig.setAspectRatio(), orstate.setState()instead of recreating the widget tree (E). - DON'T keep multiple
CameraAwesomeBuilderinstances in memory: Verify that previous routes with camera previews are popped or disposed before launching another (E).
Concurrency and async safety
Race condition prevention
- Double Shutter Taps: Users frequently tap capture buttons repeatedly. Protect capture calls with a debounce boolean or check
state.captureState?.status == MediaCaptureStatus.capturing. - State Transition Collisions: Never call
state.setState()whilestateisPreparingCameraState(throwsCameraNotReadyException). - Video Recording State Invariant: When video recording is started, you must call
recordingState.stopRecording()before changing capture mode or switching lenses (D). - Disposed Subscriptions: When listening to
sensorConfig.zoom$orsensorConfig.flashMode$inStatefulWidgetclasses, always cancel theStreamSubscriptionindispose()to prevent memory leaks (E).
Application architecture
Scale camera integration architecture according to application complexity:
1. Small / Single-purpose camera screen (e.g. Profile Photo Picker)
- Use
CameraAwesomeBuilder.awesomedirectly within a standardStatefulWidget. - Handle the captured path in
onMediaTaporonMediaCaptureEvent. - No separate service or state management layer required.
2. Medium feature (e.g. Document scanner with review step)
- Use
CameraAwesomeBuilder.customwith a dedicated presentation widget. - Extract UI controls into isolated, rebuild-scoped widgets (
ShutterButton,FlashToggle,LensSelector). - Handle business logic (saving metadata, analytics, upload queue) in a dedicated domain controller or BLoC/Notifier that receives the output
CaptureRequest.
3. Camera-heavy / Real-time computer vision app (e.g. Continuous inventory barcode scanner)
- Use
CameraAwesomeBuilder.previewOnly. - Presentation Layer: Renders camera preview and bounding box overlays via
CustomPainter. - Vision Pipeline Layer: Manages
AnalysisConfig, frame throttling, and ML Kit detector lifecycle. - Domain Layer: Processes detected barcodes, handles debouncing/deduplication, triggers haptic feedback, and interacts with backend APIs.
Error handling and recovery
| Failure scenario | Detection mechanism | Recommended recovery | User experience |
| Camera permission denied | onPermissionsResult(false) or permission_handler status check | Do not initialize camera; display an in-place placeholder with a button to request permissions or open system settings. | Clear explanatory text explaining why the camera is required with an actionable button. |
| Microphone permission denied during video | MediaCaptureStatus.failure on startRecording() | Re-check Permission.microphone; offer user the choice to record video without audio. | Dialog: "Record without audio or grant microphone permission in Settings." |
| Android LEVEL_3 hardware limitation | CameraCharacteristics.isVideoRecordingAndImageAnalysisSupported() returns false | Disable real-time image analysis before switching to video recording mode. | Smooth degradation: preview and video work without crashing. |
| Disk space exhausted / File write failure | MediaCaptureStatus.failure with IOException in onMediaCaptureEvent | Catch filesystem error, inspect free disk space with path_provider, clean temporary files. | Snackbar: "Insufficient storage space to save photo." |
| Camera hardware session collision | Crash on CameraInterface().start() when navigating between camera pages | Ensure previous camera route is fully disposed before pushing new camera screen; avoid simultaneous hero transitions. | Prevent app freeze by sequencing navigation cleanly. |
| Rapid camera switching crash | Unhandled native exception during switchCameraSensor() | Guard lens switching with a debounce flag (_isSwitchingLens) until the future resolves. | Shutter/flip button temporarily disabled until lens settles. |
Platform-specific caveats
Android
- Front Camera Mirroring: Front camera frames delivered to
onImageForAnalysisare not mirrored natively, even though the preview texture is mirrored. When computing bounding boxes for the front camera, apply an X-axis flip transformation (D). - Orientation Inversion: On Android, image width and height reported by
AnalysisImageare swapped relative to Flutter screen dimensions in portrait mode (D). - CameraX Minimum API: While
minSdkVersionis 21, CameraX features (such as 60 FPS or HDR capture) depend on vendor HAL3 implementations. Test on lower-end devices (Android Go, MediaTek chipsets) (P).
iOS
- Analysis Resolution Fixed: iOS does not support custom analysis target widths via
CupertinoAnalysisOptions. The native engine chooses an optimized resolution based on preview constraints (D). - Format Limitation: iOS image analysis exclusively supports
InputAnalysisImageFormat.bgra8888(D). - App Store Submission Requirement: If microphone is not utilized, strip the
AVAudioSessionmicrophone reference via PodfileAUDIO_SESSION_MICROPHONE=0to prevent review rejection (D).
Testing strategy
- Unit tests:
- Unit test path generation logic (
CaptureRequestBuilder). - Unit test ML Kit coordinate transformation mathematics (
convertFromImage, rect intersection logic). - Mock camera domain controllers using simulated
MediaCaptureevents.
- Unit test path generation logic (
- Widget tests:
- Widget tests running in headless environments cannot access native CameraX / AVFoundation camera textures.
- When widget testing screens containing
CameraAwesomeBuilder, mock or wrap the camera builder behind a testable abstraction, or test subcomponents (AwesomeOrientedWidget,AwesomeFlashButton) in isolation.
- Physical device testing (Mandatory for hardware validation):
- Hardware lens verification: Validate switching across all available physical lenses (ultra-wide, telephoto).
- Rotation testing: Rotate device 90, 180, and 270 degrees in both portrait and landscape orientations. Verify captured JPEG Exif orientation tags match visual expectation.
- App backgrounding: Press Home / switch apps while video is recording; verify recording terminates cleanly without corrupted files.
- Thermal & battery profile: Profile with Android Studio Profiler or Xcode Instruments during continuous 5-minute image analysis to verify FPS stability and absence of memory leaks.
Common anti-patterns
Anti-pattern 1: Launching un-awaited analysis workloads
// ❌ BAD: Launches un-awaited future; frames backlog, memory balloons, app OOMs
onImageForAnalysis: (img) async {
heavyMlModel.processImage(img.toInputImage()); // Un-awaited!
}
// ✅ GOOD: Await processing to enforce backpressure handshake with native side
onImageForAnalysis: (img) async {
await heavyMlModel.processImage(img.toInputImage());
}Anti-pattern 2: Rebuilding CameraAwesomeBuilder on configuration changes
// ❌ BAD: Changing zoom rebuilds the whole camera widget, restarting native session
Widget build(BuildContext context) {
return CameraAwesomeBuilder.custom(
sensorConfig: SensorConfig.single(zoom: _currentZoom), // State rebuild restarts camera!
// ...
);
}
// ✅ GOOD: Update properties imperatively through the existing SensorConfig
void onZoomChanged(double newZoom) {
_cameraState.sensorConfig.setZoom(newZoom);
}Anti-pattern 3: Using 1.x builder signatures in 2.x projects
// ❌ BAD: 1.x legacy signature (causes compile error in 2.x)
builder: (CameraState state, PreviewSize previewSize, Rect previewRect) { ... }
// ✅ GOOD: 2.x modern signature
builder: (CameraState state, AnalysisPreview preview) { ... }Anti-pattern 4: Processing full-resolution analysis frames
// ❌ BAD: 1080p or unconstrained width transfer saturates platform channel
AnalysisConfig(
androidOptions: const AndroidAnalysisOptions.nv21(width: 1920),
)
// ✅ GOOD: 250 - 500px width is optimal for barcode/face ML inference
AnalysisConfig(
androidOptions: const AndroidAnalysisOptions.nv21(width: 500),
maxFramesPerSecond: 10,
)Debugging playbook
| Symptom | Probable cause | Diagnostic & Fix sequence |
| Black preview screen | Camera hardware not started or permission denied | 1. Check logs for CameraNotReadyException.<br>2. Confirm NSCameraUsageDescription (iOS) or camera permissions (Android).<br>3. Verify another camera app or background process is not locking the hardware sensor. |
| Preview stretched or distorted | Mismatched preview fit or aspect ratio | 1. Set previewFit: CameraPreviewFit.contain (default in 2.4.0+).<br>2. Align sensorConfig.aspectRatio with container aspect ratio. |
| Captured image orientation rotated 90° | Native sensor orientation mismatch | 1. Ensure exifPreferences is preserved.<br>2. Verify viewing widget respects image Exif metadata. |
| Analysis callback not firing | AnalysisConfig.autoStart is false, or previous frame future never completed | 1. Verify autoStart: true or explicitly call state.analysisController?.start().<br>2. Verify onImageForAnalysis callback is not hanging on an uncompleted future. |
| App Store review rejected (Microphone) | Unused AVAudioSession symbol detected on iOS | Add AUDIO_SESSION_MICROPHONE=0 preprocessor definition to ios/Podfile post_install block. |
| High CPU / Device overheating | Unthrottled image analysis stream | 1. Set maxFramesPerSecond: 10 or lower.<br>2. Reduce androidOptions.width to 250 or 500.<br>3. Call analysisController?.stop() when scanning is paused. |
Decision tables
Builder constructor selection
Need in-app camera?
├── Simple profile photo / video from gallery? ──> Use image_picker (do not use CamerAwesome)
└── Need real-time camera viewfinder?
├── Turn-key camera UI with standard buttons? ──> CameraAwesomeBuilder.awesome
├── Custom branded UI, custom shutter, overlays? ──> CameraAwesomeBuilder.custom
├── Barcode / Face / ML scanner with preview? ──> CameraAwesomeBuilder.previewOnly
└── Background / Canvas filtering without preview? ──> CameraAwesomeBuilder.analysisOnlyAnalysis format and resolution selection
| Platform | Target ML Model | Recommended format | Recommended width | Target FPS |
| Android | Barcode / QR scanning | AndroidAnalysisOptions.nv21 | 500 | 10 - 15 |
| Android | Face detection | AndroidAnalysisOptions.nv21 | 250 - 500 | 15 |
| Android | Image filtering / Preview | AndroidAnalysisOptions.yuv420 | 500 - 1000 | 30 |
| iOS | Barcode / Face / Vision | CupertinoAnalysisOptions.bgra8888 | Native auto | 10 - 15 |
AI coding-agent workflow
When tasked with implementing or modifying CamerAwesome code in a Flutter project, follow this exact operational sequence:
[1. Inspect Repository] ──> Read pubspec.yaml, check camerawesome version (must be 2.x).
│
[2. Verify Platforms] ──> Ensure Android & iOS only. Abort if desktop or web requested.
│
[3. Check Native Setup] ──> Verify Info.plist descriptions, AndroidManifest, minSdkVersion 21+.
│
[4. Select Builder] ──> Choose .awesome, .custom, .previewOnly, or .analysisOnly.
│
[5. Design Lifecycle] ──> Ensure single camera session; isolate stream rebuilds.
│
[6. Configure Capture] ──> Set SaveConfig, paths, Exif, and VideoOptions.
│
[7. Throttle Analysis] ──> Set maxFramesPerSecond <= 15 and width <= 500 if using ML.
│
[8. Implement UI] ──> Use AwesomeOrientedWidget for rotating controls.
│
[9. Verify & Test] ──> Run flutter analyze; check hardware camera on real device.Code-review checklist
Apply this checklist before approving any CamerAwesome PR or task:
- Package version: Does code match
2.xAPIs (CameraLayoutBuilder = Widget Function(CameraState, AnalysisPreview))? - Platform isolation: Are unsupported platforms (Web, macOS, Windows, Linux) guarded against importing or executing CamerAwesome?
- Permissions declared: Are
NSCameraUsageDescriptionandNSMicrophoneUsageDescription(if recording video) declared inInfo.plist? - Microphone stripped if unused: Is
AUDIO_SESSION_MICROPHONE=0set inios/Podfilefor preview-only / barcode-only apps? - Android SDK level: Is
minSdkVersion >= 21configured inandroid/app/build.gradle? - No duplicate sessions: Are routes structured so that two
CameraAwesomeBuilderinstances never exist simultaneously? - Rebuild boundaries: Are shutter, zoom, and flash buttons isolated with
StreamBuilderrather than rebuildingCameraAwesomeBuilder? - Analysis backpressure: Is
onImageForAnalysisproperlyawaited to prevent unbounded frame queueing? - Analysis rate bounded: Is
maxFramesPerSecondexplicitly configured inAnalysisConfig? - Coordinate mapping: Are bounding box coordinates mapped using
preview.convertFromImage(...)rather than raw screen offsets? - Subscription cleanup: Are all application-created
StreamSubscriptioninstances cancelled indispose()? - Video stop invariant: Does the UI prevent state changes while video is actively recording without calling
stopRecording()first?
Reference implementations
1. Production-ready photo and video camera with custom controls
import 'dart:io';
import 'package:camerawesome/camerawesome_plugin.dart';
import 'package:camerawesome/pigeon.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
class ProductionCameraScreen extends StatefulWidget {
const ProductionCameraScreen({super.key});
@override
State<ProductionCameraScreen> createState() => _ProductionCameraScreenState();
}
class _ProductionCameraScreenState extends State<ProductionCameraScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: CameraAwesomeBuilder.custom(
saveConfig: SaveConfig.photoAndVideo(
initialCaptureMode: CaptureMode.photo,
mirrorFrontCamera: true,
photoPathBuilder: (sensors) async {
final dir = await getApplicationDocumentsDirectory();
final filePath = '${dir.path}/photo_${DateTime.now().millisecondsSinceEpoch}.jpg';
return SingleCaptureRequest(filePath, sensors.first);
},
videoPathBuilder: (sensors) async {
final dir = await getApplicationDocumentsDirectory();
final filePath = '${dir.path}/video_${DateTime.now().millisecondsSinceEpoch}.mp4';
return SingleCaptureRequest(filePath, sensors.first);
},
videoOptions: VideoOptions(
enableAudio: true,
quality: VideoRecordingQuality.fhd,
android: AndroidVideoOptions(
bitrate: 6000000,
fallbackStrategy: QualityFallbackStrategy.lower,
),
),
),
sensorConfig: SensorConfig.single(
sensor: Sensor.position(SensorPosition.back),
aspectRatio: CameraAspectRatios.ratio_4_3,
flashMode: FlashMode.none,
),
previewFit: CameraPreviewFit.cover,
builder: (CameraState state, AnalysisPreview preview) {
return SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Top controls bar
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AwesomeOrientedWidget(
child: StreamBuilder<FlashMode>(
stream: state.sensorConfig.flashMode$,
builder: (context, snapshot) {
final flash = snapshot.data ?? FlashMode.none;
return IconButton(
icon: Icon(
flash == FlashMode.none ? Icons.flash_off : Icons.flash_on,
color: Colors.white,
),
onPressed: () => state.sensorConfig.switchCameraFlash(),
);
},
),
),
AwesomeOrientedWidget(
child: StreamBuilder<CameraAspectRatios>(
stream: state.sensorConfig.aspectRatio$,
builder: (context, snapshot) {
final ratio = snapshot.data ?? CameraAspectRatios.ratio_4_3;
return TextButton(
onPressed: () => state.sensorConfig.switchCameraRatio(),
child: Text(
ratio.name.replaceAll('ratio_', '').replaceAll('_', ':'),
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
);
},
),
),
],
),
),
// Bottom control panel
Padding(
padding: const EdgeInsets.only(bottom: 24),
child: Column(
children: [
// Mode Selector (Photo / Video)
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ChoiceChip(
label: const Text('PHOTO'),
selected: state.captureMode == CaptureMode.photo,
onSelected: (_) => state.setState(CaptureMode.photo),
),
const SizedBox(width: 12),
ChoiceChip(
label: const Text('VIDEO'),
selected: state.captureMode == CaptureMode.video,
onSelected: (_) => state.setState(CaptureMode.video),
),
],
),
const SizedBox(height: 16),
// Shutter Row
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
const SizedBox(width: 48), // Spacer
_ShutterButton(state: state),
AwesomeOrientedWidget(
child: IconButton(
icon: const Icon(Icons.flip_camera_ios, color: Colors.white, size: 32),
onPressed: () => state.switchCameraSensor(),
),
),
],
),
],
),
),
],
),
);
},
),
);
}
}
class _ShutterButton extends StatelessWidget {
final CameraState state;
const _ShutterButton({required this.state});
@override
Widget build(BuildContext context) {
return state.when(
onPhotoMode: (photoState) => GestureDetector(
onTap: () async {
await photoState.takePhoto(
onPhoto: (req) => debugPrint('Saved: ${req.path}'),
onPhotoFailed: (err) => debugPrint('Error: $err'),
);
},
child: Container(
width: 76,
height: 76,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 4),
color: Colors.white24,
),
child: Center(
child: Container(
width: 58,
height: 58,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
),
),
),
),
onVideoMode: (videoState) => GestureDetector(
onTap: () => videoState.startRecording(),
child: Container(
width: 76,
height: 76,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.red, width: 4),
),
child: Center(
child: Container(
width: 58,
height: 58,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.red,
),
),
),
),
),
onVideoRecordingMode: (recordingState) => GestureDetector(
onTap: () => recordingState.stopRecording(),
child: Container(
width: 76,
height: 76,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.red, width: 4),
),
child: Center(
child: Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(4),
),
),
),
),
),
) ?? const SizedBox.shrink();
}
}2. High-performance ML Kit barcode scanner with coordinate reticle
import 'package:camerawesome/camerawesome_plugin.dart';
import 'package:flutter/material.dart';
import 'package:google_mlkit_barcode_scanning/google_mlkit_barcode_scanning.dart';
class BarcodeScannerScreen extends StatefulWidget {
const BarcodeScannerScreen({super.key});
@override
State<BarcodeScannerScreen> createState() => _BarcodeScannerScreenState();
}
class _BarcodeScannerScreenState extends State<BarcodeScannerScreen> {
final BarcodeScanner _scanner = BarcodeScanner(formats: [BarcodeFormat.all]);
String? _scannedValue;
Rect? _barcodeBoundingBox;
bool _isProcessing = false;
@override
void dispose() {
_scanner.close();
super.dispose();
}
Future<void> _handleImageAnalysis(AnalysisImage image, AnalysisPreview preview) async {
if (_isProcessing || _scannedValue != null) return;
_isProcessing = true;
try {
final inputImage = _convertToInputImage(image);
final List<Barcode> barcodes = await _scanner.processImage(inputImage);
if (barcodes.isNotEmpty) {
final barcode = barcodes.first;
if (barcode.cornerPoints.length >= 4) {
final topLeft = preview.convertFromImage(
Offset(barcode.cornerPoints[0].x.toDouble(), barcode.cornerPoints[0].y.toDouble()),
image,
);
final bottomRight = preview.convertFromImage(
Offset(barcode.cornerPoints[2].x.toDouble(), barcode.cornerPoints[2].y.toDouble()),
image,
);
if (mounted) {
setState(() {
_scannedValue = barcode.rawValue;
_barcodeBoundingBox = Rect.fromPoints(topLeft, bottomRight);
});
}
}
}
} catch (e) {
debugPrint('ML Kit analysis error: $e');
} finally {
_isProcessing = false;
}
}
InputImage _convertToInputImage(AnalysisImage image) {
return image.when(
nv21: (nv21) => InputImage.fromBytes(
bytes: nv21.bytes,
metadata: InputImageMetadata(
size: nv21.size,
rotation: InputImageRotation.values.byName(image.rotation.name),
format: InputImageFormat.nv21,
bytesPerRow: nv21.planes.first.bytesPerRow,
),
),
bgra8888: (bgra) => InputImage.fromBytes(
bytes: bgra.bytes,
metadata: InputImageMetadata(
size: bgra.size,
rotation: InputImageRotation.values.byName(image.rotation.name),
format: InputImageFormat.bgra8888,
bytesPerRow: bgra.planes.first.bytesPerRow,
),
),
)!;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Scan QR / Barcode')),
body: CameraAwesomeBuilder.previewOnly(
sensorConfig: SensorConfig.single(
sensor: Sensor.position(SensorPosition.back),
aspectRatio: CameraAspectRatios.ratio_16_9,
),
imageAnalysisConfig: AnalysisConfig(
androidOptions: const AndroidAnalysisOptions.nv21(width: 500),
cupertinoOptions: const CupertinoAnalysisOptions.bgra8888(),
maxFramesPerSecond: 10,
autoStart: true,
),
builder: (CameraState state, AnalysisPreview preview) {
return Stack(
children: [
if (_barcodeBoundingBox != null)
Positioned.fromRect(
rect: _barcodeBoundingBox!,
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.greenAccent, width: 3),
borderRadius: BorderRadius.circular(8),
),
),
),
if (_scannedValue != null)
Positioned(
bottom: 40,
left: 20,
right: 20,
child: Card(
color: Colors.black87,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
_scannedValue!,
style: const TextStyle(color: Colors.white, fontSize: 16),
),
const SizedBox(height: 12),
ElevatedButton(
onPressed: () {
setState(() {
_scannedValue = null;
_barcodeBoundingBox = null;
});
},
child: const Text('Scan Again'),
),
],
),
),
),
),
],
);
},
onImageForAnalysis: (img) async {
// Note: In custom pipelines, pass current preview to handler
},
),
);
}
}Quick-reference rules
- DO verify the installed
camerawesomeversion before writing code; never mix1.xwith2.xAPIs. - DO configure
maxFramesPerSecond(5–15 FPS) inAnalysisConfigto avoid CPU thermal throttling and memory growth. - DO set
width: 500on AndroidAnalysisConfigfor computer vision workloads. - DO
awaitinference calls insideonImageForAnalysisto maintain the native backpressure handshake. - DO wrap control buttons in
AwesomeOrientedWidgetso they rotate cleanly when the user turns the phone. - DO use
preview.convertFromImage(...)to map detected ML points into Flutter screen coordinates. - DO strip microphone symbols in
ios/PodfileviaAUDIO_SESSION_MICROPHONE=0for preview-only apps. - DON'T configure CamerAwesome for Web, Windows, macOS, or Linux targets; it supports Android and iOS only.
- DON'T rebuild the parent widget of
CameraAwesomeBuilderto modify zoom, flash, or aspect ratio. - DON'T instantiate multiple active
CameraAwesomeBuilderwidgets concurrently across navigation routes. - DON'T call
state.setState()while the camera is inPreparingCameraState. - DON'T rely on unit tests to prove camera functionality; validate on real iOS and Android hardware.
Official references
- CamerAwesome on Pub.dev: Verified latest release (
2.5.0), SDK constraints, and supported platforms. - Official CamerAwesome Documentation: Verified architecture, builders, theming, ML Kit integration, and coordinate mapping.
- Official CamerAwesome GitHub Repository: Verified state classes, Pigeon bridge definitions, CameraX 1.4.2 setup, and sample implementations.
- Android CameraX Documentation: Verified hardware LEVEL_3 use-case combination constraints for simultaneous video and image analysis.
- Apple AVFoundation Documentation: Verified iOS 12.0+ deployment target and
AVAudioSessionApp Store review guidelines.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

