Open Source

Flutter package

camera_extended

Flutter's camera plugin, forked to add native aspect-ratio capture — 16:9, 4:3 and 1:1 at the sensor level on Android and iOS.

v1.2.522.7k / month16 likesBSD-3-ClauseAndroid · iOS16:9 · 4:3 · 1:1

Aspect ratio guide

Using CameraAspectRatio for preview, photo and video on Android (CameraX) and iOS (AVFoundation) — the four ratios and how each affects capture.

source: packages/camera/README.md

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.

Set a ratiodart
final controller = CameraController(
  cameras.first,
  ResolutionPreset.high,
  enableAudio: false,
  aspectRatio: CameraAspectRatio.ratio1x1, // square capture
);

await controller.initialize();
RatioWhat happens at the sensorBest for
ratio16x9Native widescreen format on both platforms (CameraX / AVFoundation).Video and widescreen capture.
ratio4x3Native standard format on both platforms; more vertical field of view than 16:9.Photos and documents.
ratio1x1Native square on Android (1088×1088, 720×720); iOS has no native square format and falls back to 4:3.Social media and profile photos.
ratioDefaultThe 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.

Reinitialize on changedart
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.

dart
// 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 video

No 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.

dart
// 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.

dart
// 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.

Three stepsdart
# 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.

dart
@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 ratio1x1 falls 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; with allowFallback: false an unsupported mode throws an ArgumentError.
  • Add permissions before initializing. Android needs CAMERA (and RECORD_AUDIO for video); iOS needs NSCameraUsageDescription and NSMicrophoneUsageDescription in Info.plist.
  • Requested ratios aren't guaranteed. When an exact format isn't available the platform uses the closest one, so verify with controller.value.aspectRatio if it matters.

Let's make something together.

If you have any questions about a new project or other inquiries, feel free to contact us. We will get back to you as soon as possible.

Codigee
We are using cookies. Learn more