camera_with_gps

pub package pub points likes license

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,0 coordinates and Samsung's 1970-01-01 placeholder 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 standard ImagePicker strips;
  • detects 0, 0 coordinates and the 1970-01-01 date 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 CameraController with a different CameraDescription returned by availableCameras() โ€” not implemented out of the box.
  • Pure digital zoom only โ€” no built-in support for AVFoundation's builtInTripleCamera smooth 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.

  1. Fork the repository.
  2. Create a feature branch: git checkout -b feature/your-feature.
  3. Commit: git commit -m 'Add some feature'.
  4. Push: git push origin feature/your-feature.
  5. Open a Pull Request.

License

MIT โ€” see LICENSE.

Author

Ruslan Madzhara ยท LinkedIn