camera_with_gps
A Flutter plugin that opens a full-screen camera UI and embeds the device's current GPS coordinates into the captured photo's EXIF metadata. Includes pinch-to-zoom, flash, aspect-ratio toggle, and a smart gallery picker.
Features
- ๐ธ Full-screen camera UI with shutter, flash, and camera switching.
- ๐ GPS coordinates written straight into the photo's EXIF tags (
GPSLatitude,GPSLongitude,GPSLatitudeRef,GPSLongitudeRef). - ๐ Pinch-to-zoom + preset buttons (
0.5x/1x/2x), automatically filtered to what the active camera actually supports. - ๐ฆ Flash / torch control.
- ๐ Front / back camera switching.
- ๐ 16:9 โ 4:3 aspect-ratio toggle.
- ๐ผ๏ธ Gallery picker (optional toggle) โ uses Android SAF on Samsung devices and the standard image picker elsewhere.
- โ ๏ธ GPS status banner when location services are disabled or permission is denied โ the camera keeps working, GPS is just skipped.
- ๐งน Fake-GPS scrubbing โ
0,0coordinates and Samsung's1970-01-01placeholder are detected and removed. - ๐ฑ Orientation-aware photo rotation via on-device sensors, with platform- specific cropping logic.
- โก Low shutter latency โ location is pre-warmed in the background and bounded with a 2 s timeout so airplane-mode / cold-start GPS no longer blocks capture.
Installation
Add the dependency:
dependencies:
camera_with_gps: ^2.5.0
Then:
flutter pub get
Android
Minimum SDK: 21. Add to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
iOS
Minimum deployment target: 12.0. Add to ios/Runner/Info.plist:
<key>NSCameraUsageDescription</key>
<string>This app needs camera access to take photos with GPS metadata.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs location access to add GPS metadata to photos.</string>
Quick start
import 'package:camera_with_gps/camera_with_gps.dart';
import 'package:flutter/material.dart';
class CapturePage extends StatelessWidget {
const CapturePage({super.key});
Future<void> _shoot(BuildContext context) async {
final path = await CameraWithGps.openCamera(context);
if (path != null) {
// ...use the JPEG file at `path`.
}
}
@override
Widget build(BuildContext context) => Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => _shoot(context),
child: const Icon(Icons.camera_alt),
),
);
}
A complete sample lives under example/.
API
// Open the full-screen camera. `allowGallery` toggles the gallery button.
static Future<String?> openCamera(BuildContext context, {bool allowGallery = true});
// Shortcut: open the camera without the gallery button.
static Future<String?> openCameraPhotoOnly(BuildContext context);
// Pick an existing image (Samsung โ SAF, others โ standard gallery).
static Future<String?> pickFromGallery();
// Write GPS coordinates into the EXIF metadata of an existing JPEG.
static Future<bool> addGps({
required String path,
required double latitude,
required double longitude,
});
// Strip GPS metadata from a JPEG.
static Future<bool> removeGps({required String path});
All Future<String?> results are JPEG file paths (or null on cancel).
Behavior
GPS attachment. When the shutter is pressed, the plugin tries โ in order โ
(1) the pre-warmed position kept fresh by a background stream, (2) the OS's
last-known position, (3) a getCurrentPosition call with a 2 s timeout. If
none yield a valid fix, any existing GPS tags on the captured file are scrubbed
so the photo never ships with stale or fake coordinates.
Zoom range. Calls getMinZoomLevel() / getMaxZoomLevel() on init and on
camera switch. Preset buttons (0.5x, 1x, 2x) appear only when they fall
inside that range. On iOS the wide-angle lens reports min == 1.0, so the
0.5x button shows only when the active camera is a different lens. On Android
CameraX handles automatic lens switching internally on multi-lens flagships
(Pixel 6+, Galaxy S21+), so setZoomLevel(0.6) may transparently engage the
ultra-wide camera.
Orientation. The UI is locked to portrait. Photo rotation is computed from
sensor orientation (OrientationService) and applied per-platform
(PhotoProcessorAndroid / PhotoProcessorIOS).
Samsung handling. Galleries on Samsung devices sometimes drop EXIF GPS or inject placeholder values. The plugin:
- routes the gallery picker through
ACTION_OPEN_DOCUMENT(SAF) on Samsung, which preserves metadata that the standardImagePickerstrips; - detects
0, 0coordinates and the1970-01-01date stamp as fake-GPS markers and removes them before returning the path.
Reading the embedded EXIF
import 'dart:io';
import 'package:exif/exif.dart';
final bytes = await File(path).readAsBytes();
final tags = await readExifFromBytes(bytes);
final gps = {
for (final e in tags.entries)
if (e.key.startsWith('GPS')) e.key: e.value.printable,
};
Project layout
lib/
โโโ camera_with_gps.dart โ public library entry (barrel)
โ
โโโ pages/
โ โโโ camera_preview_page.dart โ stateful camera shell
โ
โโโ services/
โ โโโ camera_with_gps.dart โ static API class
โ โโโ orientation_service.dart โ sensor-driven orientation stream
โ โโโ photo_processor.dart โ platform facade
โ โโโ photo_processor_android.dart
โ โโโ photo_processor_ios.dart
โ
โโโ widgets/
โโโ bottom_bar.dart โ shutter row + zoom presets slot
โโโ top_bar.dart โ close / flash / ratio
โโโ preview_box.dart โ platform facade for live preview
โโโ preview_box_android.dart
โโโ preview_box_ios.dart
โโโ zoom_presets.dart โ 0.5x / 1x / 2x pill
โโโ shutter_button.dart
โโโ rot_icon.dart โ orientation-aware icon
โโโ gps_banner.dart
โโโ error_ui.dart
Limitations
- The plugin uses the wide-angle back camera by default. On iPhones with
multiple physical lenses, switching to ultra-wide or telephoto requires
recreating the
CameraControllerwith a differentCameraDescriptionreturned byavailableCameras()โ not implemented out of the box. - Pure digital zoom only โ no built-in support for AVFoundation's
builtInTripleCamerasmooth lens transitions. - Gallery-picked images are returned as-is; the plugin does not try to add GPS
data to them. It will only strip fake/placeholder GPS detected via
checkGps.
Contributing
Issues and pull requests are welcome at github.com/RuslanMadzhara/camera_with_gps.
- Fork the repository.
- Create a feature branch:
git checkout -b feature/your-feature. - Commit:
git commit -m 'Add some feature'. - Push:
git push origin feature/your-feature. - Open a Pull Request.
License
MIT โ see LICENSE.
Author
Ruslan Madzhara ยท LinkedIn
Libraries
- camera_with_gps
- pages/camera_preview_page
- services/camera_with_gps
- services/orientation_service
- services/photo_processor
- services/photo_processor_android
- services/photo_processor_ios
- utils/camera_with_gps_method_channel
- utils/camera_with_gps_platform_interface
- widgets/bottom_bar
- widgets/error_ui
- widgets/preview_box
- widgets/preview_box_android
- widgets/preview_box_ios
- widgets/rot_icon
- widgets/top_bar
- widgets/zoom_presets