Guide
Choosing a ratio
Pass one of the four CameraAspectRatio values to the CameraController constructor. camera_extended asks the native camera to capture at that shape — CameraX picks a matching format on Android, AVFoundation selects the closest format on iOS — rather than cropping a frame afterwards, so you keep the full field of view the sensor offers at that ratio.
final controller = CameraController(
cameras.first,
ResolutionPreset.high,
enableAudio: false,
aspectRatio: CameraAspectRatio.ratio1x1, // square capture
);
await controller.initialize();| Ratio | What happens at the sensor | Best for |
|---|---|---|
ratio16x9 | Native widescreen format on both platforms (CameraX / AVFoundation). | Video and widescreen capture. |
ratio4x3 | Native standard format on both platforms; more vertical field of view than 16:9. | Photos and documents. |
ratio1x1 | Native square on Android (1088×1088, 720×720); iOS has no native square format and falls back to 4:3. | Social media and profile photos. |
ratioDefault | The camera's native ratio (typically 4:3 on mobile). No specific request is made. | When you don't need a particular shape. |
iOS 1:1
iOS cameras have no native 1:1 format, so ratio1x1 resolves to 4:3 on iOS. If you need a truly square result on iOS, capture at 4:3 and crop to square in your own code.
Reinitialize
Switching ratios at runtime
The aspect ratio is fixed at the sensor when the controller initializes, so it can't be changed in place. To switch, dispose the current controller and create a new one with the new ratio. This is exactly what the example app does.
CameraAspectRatio _ratio = CameraAspectRatio.ratio4x3;
Future<void> _initCamera() async {
// Dispose the old controller before creating a new one.
await _controller?.dispose();
_controller = CameraController(
cameras.first,
ResolutionPreset.high,
enableAudio: false,
aspectRatio: _ratio,
);
await _controller!.initialize();
if (mounted) setState(() {});
}
void _onRatioChanged(CameraAspectRatio? ratio) {
if (ratio == null || ratio == _ratio) return;
setState(() => _ratio = ratio);
_initCamera(); // reinitialize — the sensor ratio can't change in place
}Always dispose first
Call await _controller?.dispose() before creating the replacement, and guard setState with mounted since initialize() is async and can complete after the widget is gone.
One ratio everywhere
Preview, photo & video
Because the ratio is configured on the sensor, the preview, still photos and recorded video all come out at the same shape from a single request — no separate preview vs. capture ratio to reconcile. CameraPreview sizes itself to controller.value.aspectRatio, which reflects the actual preview dimensions the platform returned.
// One request configures the sensor; preview, stills and video all share it.
final controller = CameraController(
cameras.first,
ResolutionPreset.high,
aspectRatio: CameraAspectRatio.ratio16x9,
);
await controller.initialize();
CameraPreview(controller); // 16:9 preview
final XFile photo = await controller.takePicture(); // 16:9 photo
await controller.startVideoRecording(); // 16:9 videoNo file I/O
In-memory capture
When you need the pixels rather than a file, use takePictureAsBytes(). It returns the JPEG-encoded image as a Uint8List without touching the filesystem — ideal for on-device ML, image processing or uploading straight to an API.
// Skip the filesystem entirely.
final Uint8List bytes = await controller.takePictureAsBytes();
// Feed an ML/vision pipeline or upload directly.
await api.uploadImage(bytes);Per-device
Video stabilization
Stabilization support varies by device, so query getSupportedVideoStabilizationModes() before applying one. The levels are off, level1, level2 and level3 (increasing stabilization, increasing latency). Leave allowFallback at its default to have the controller step down to a supported level automatically.
// Query what the device actually supports first.
final modes = await controller.getSupportedVideoStabilizationModes();
// Apply the strongest supported level.
if (modes.contains(VideoStabilizationMode.level3)) {
await controller.setVideoStabilizationMode(VideoStabilizationMode.level3);
}
// Or request a level and let the controller fall back automatically.
await controller.setVideoStabilizationMode(
VideoStabilizationMode.level3,
allowFallback: true,
);
await controller.startVideoRecording();From camera
Migrating from the camera plugin
camera_extended is API-compatible with the official camera package, so migration is a dependency swap and an import change. Existing controller code keeps compiling and behaving the same; you opt into aspect ratios where you want them.
# pubspec.yaml
# - camera: ^0.11.0
# + camera_extended: ^1.2.4
// Update the import
// - import 'package:camera/camera.dart';
import 'package:camera_extended/camera_extended.dart';
// Existing code keeps working; add aspectRatio when you want it.
CameraController(
camera,
ResolutionPreset.high,
aspectRatio: CameraAspectRatio.ratio4x3,
);Don't leak the camera
App lifecycle
Handle lifecycle changes exactly as with the original plugin: dispose the controller when the app goes inactive and reinitialize it on resume so you release the camera in the background.
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
final CameraController? cameraController = _controller;
if (cameraController == null || !cameraController.value.isInitialized) {
return;
}
if (state == AppLifecycleState.inactive) {
cameraController.dispose();
} else if (state == AppLifecycleState.resumed) {
_initCamera();
}
}Watch out
Common gotchas
- 1:1 is Android-only at the sensor. On iOS
ratio1x1falls back to 4:3 — don't assume a square result on every device. - Changing ratio needs a reinitialize. You must dispose and recreate the controller; updating a field won't re-shape a live session.
- Stabilization is device-dependent.
getSupportedVideoStabilizationModes()can return an empty list; withallowFallback: falsean unsupported mode throws anArgumentError. - Add permissions before initializing. Android needs
CAMERA(andRECORD_AUDIOfor video); iOS needsNSCameraUsageDescriptionandNSMicrophoneUsageDescriptioninInfo.plist. - Requested ratios aren't guaranteed. When an exact format isn't available the platform uses the closest one, so verify with
controller.value.aspectRatioif it matters.
