device_geolocation 1.1.0 copy "device_geolocation: ^1.1.0" to clipboard
device_geolocation: ^1.1.0 copied to clipboard

Cross-platform Flutter geolocation plugin (Android, iOS, macOS, Web, Windows, Linux).

device_geolocation #

pub package CI codecov License: MIT

A cross-platform Flutter plugin that provides easy access to the device's geolocation across Android, iOS, macOS, Web, Windows and Linux.

Credits & attribution — the public API surface, semantics and naming of this plugin are inspired by the excellent flutter-geolocator plugin by Baseflow. This package is an independent reimplementation distributed under the MIT License.

Features #

  • One-shot, last-known and streaming positions.
  • Permission lifecycle (checkPermission, requestPermission) with optional Android background-location escalation.
  • Service-status stream for OS-level location toggles.
  • iOS 14+ temporary full-accuracy requests.
  • Distance and bearing helpers via the Haversine formula.

Platform support #

Platform Minimum version Backend
Android 7.0 (API 24) Fused Location Provider with LocationManager fallback
iOS 14.0 CLLocationManager + CLLocationUpdate.liveUpdates (17+)
macOS 11.0 CLLocationManager + CLLocationUpdate.liveUpdates (14+)
Web Secure context navigator.geolocation + navigator.permissions
Windows 10 (1809+) WinRT Windows.Devices.Geolocation
Linux GeoClue2 org.freedesktop.GeoClue2 over D-Bus

Toolchain compatibility #

This plugin is built to be forward-compatible with the latest mobile build toolchains:

  • Swift Package Manager — the iOS and macOS plugins are distributed as Swift packages (ios/device_geolocation/Package.swift, macos/device_geolocation/Package.swift), so they integrate natively when Flutter resolves your app with SwiftPM (the default on recent Flutter versions) without requiring a CocoaPods install. Traditional CocoaPods resolution is still supported through the bundled .podspec files.
  • Gradle 9 — the Android side targets the AGP 9 / Gradle 9 toolchain (Kotlin 2.x, Java 17 toolchain, namespace-based manifests). No legacy package= attributes, no deprecated compile/testCompile configurations, and no buildscript apply blocks: the module builds cleanly with the declarative Kotlin DSL (build.gradle.kts).

Functionality matrix #

All APIs are part of the same façade — the table below clarifies which capabilities are actually wired on each platform.

Feature Android iOS macOS Web Windows Linux
checkPermission / requestPermission ✅¹ ✅²
Background permission escalation ✅³
isLocationServiceEnabled
getLastKnownPosition —⁴
getCurrentPosition
getPositionStream
Background foreground service —⁹
getServiceStatusStream —⁵
getLocationAccuracy ✅⁶ ✅⁶ ✅⁶
requestTemporaryFullAccuracy —⁷ —⁷ —⁷ —⁷
openAppSettings / openLocationSettings —⁸
Distance / bearing helpers

¹ Uses the browser Permissions API where available; falls back to whileInUse after a successful position fix.
² Linux exposes a single whileInUse-like permission once the GeoClue agent has approved the app.
³ iOS Always authorization (use NSLocationAlwaysAndWhenInUseUsageDescription).
⁴ The browser Geolocation API does not expose a cached last position; this returns null.
⁵ The browser does not expose a service-status event; the stream emits the initial status and stays open.
⁶ Always reports precise (or unknown if permission is denied) because the platform does not differentiate reduced vs. precise modes.
⁷ Temporary full-accuracy is an iOS/macOS-only concept; the call is a no-op that returns the current accuracy on other platforms.
⁸ Browsers do not allow programmatic navigation to permission settings; the call returns false.
⁹ iOS keeps location running in the background via the platform's own UIBackgroundModes + allowBackgroundLocationUpdates flag — see the iOS permissions section above.

Install #

dependencies:
  device_geolocation: ^1.1.0

Permissions setup #

Android (AndroidManifest.xml) #

Add the permissions your app needs to android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Only if you need background location on Android 10+ -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

iOS (Info.plist) #

<key>NSLocationWhenInUseUsageDescription</key>
<string>We need your location to show nearby content.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>We use your location in the background to ...</string>

macOS #

In macos/Runner/Info.plist:

<key>NSLocationUsageDescription</key>
<string>We need your location to ...</string>

And enable the Location capability in macos/Runner/DebugProfile.entitlements and macos/Runner/Release.entitlements:

<key>com.apple.security.personal-information.location</key>
<true/>

Web #

The Geolocation API only works on HTTPS or localhost. No additional configuration is required.

Windows #

Enable the Location capability for your packaged app.

Linux #

Requires the geoclue-2.0 service (installed by default on most desktop distributions). No build-time configuration is needed.

Usage #

import 'package:device_geolocation/device_geolocation.dart';

Future<void> locateMe() async {
  if (!await DeviceGeolocation.isLocationServiceEnabled()) {
    await DeviceGeolocation.openLocationSettings();
    return;
  }

  var permission = await DeviceGeolocation.checkPermission();
  if (permission == LocationPermission.denied) {
    permission = await DeviceGeolocation.requestPermission();
  }
  if (permission == LocationPermission.deniedForever) {
    await DeviceGeolocation.openAppSettings();
    return;
  }

  final position = await DeviceGeolocation.getCurrentPosition(
    locationSettings: const LocationSettings(accuracy: LocationAccuracy.high),
  );
  print('${position.latitude}, ${position.longitude}');
}

Streaming positions #

final subscription = DeviceGeolocation.getPositionStream(
  locationSettings: const LocationSettings(
    accuracy: LocationAccuracy.high,
    distanceFilter: 10,
  ),
).listen((position) {
  print('${position.latitude}, ${position.longitude}');
});

// later
await subscription.cancel();

Distance and bearing #

final meters = DeviceGeolocation.distanceBetween(
    52.2165157, 6.9437819, 52.3546274, 4.8285838);
final bearing = DeviceGeolocation.bearingBetween(
    52.2165157, 6.9437819, 52.3546274, 4.8285838);

Requesting background permission on Android #

final permission = await DeviceGeolocation.requestPermission(
  requestBackground: true,
);

The plugin first requests the foreground permission and — on Android 10+ (API 29) — chains the background permission request only when foreground access has been granted, matching Google's recommended flow.

Background location on Android (foreground service) #

From 1.1.0 the plugin can promote an active location stream to an Android FOREGROUND_SERVICE_TYPE_LOCATION service so updates keep flowing while your app is in the background, the screen is off or the user switches to another task. Pass a ForegroundNotificationConfig inside AndroidSettings.foregroundNotificationConfig:

final subscription = DeviceGeolocation.getPositionStream(
  locationSettings: const AndroidSettings(
    accuracy: LocationAccuracy.high,
    distanceFilter: 10,
    intervalDuration: Duration(seconds: 5),
    foregroundNotificationConfig: ForegroundNotificationConfig(
      notificationTitle: 'Tracking your run',
      notificationText: 'We will keep recording while the screen is off.',
      notificationChannelName: 'Activity tracking',
      notificationIcon: AndroidResource(
        name: 'ic_stat_notify',
        defType: 'drawable',
      ),
      enableWakeLock: true,
      enableWifiLock: false,
      setOngoing: true,
      color: 0xFF2196F3,
    ),
  ),
).listen((position) => print(position));

The plugin starts a bound service that holds the location subscription, shows the (mandatory) notification described by the config and stops as soon as every Flutter engine has cancelled its stream. Multiple subscriptions across multiple Flutter engines are supported: the service fans every fix out to all active sinks and only tears itself down when the last one cancels.

The service automatically picks the best available location source on each device — FusedLocationProviderClient from Google Play services when they are installed, and the system LocationManager (GPS / network providers) on devices without Play services or when you pass forceLocationManager: true. Background tracking therefore works on GMS and non-GMS devices alike.

Required setup in the host app

The service itself is declared in the plugin's AndroidManifest.xml, but Google Play and Android still require the consumer app to declare the matching permissions. Add these to your android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<!-- Optional, only required when you enable enableWakeLock. -->
<uses-permission android:name="android.permission.WAKE_LOCK" />

On Android 14 (API 34) and above the runtime enforces FOREGROUND_SERVICE_LOCATION. If the host manifest does not declare it, the stream emits a PositionUpdateException with the code MISSING_FOREGROUND_SERVICE_LOCATION_PERMISSION. Make sure your Google Play listing also justifies background location usage, as the store policy requires for any app shipping this permission.

API reference #

Member Returns
DeviceGeolocation.checkPermission() Future<LocationPermission>
DeviceGeolocation.requestPermission({requestBackground}) Future<LocationPermission>
DeviceGeolocation.isLocationServiceEnabled() Future<bool>
DeviceGeolocation.getLastKnownPosition({forceLocationManager}) Future<Position?>
DeviceGeolocation.getCurrentPosition({locationSettings}) Future<Position>
DeviceGeolocation.getPositionStream({locationSettings}) Stream<Position>
DeviceGeolocation.getServiceStatusStream() Stream<ServiceStatus>
DeviceGeolocation.getLocationAccuracy() Future<LocationAccuracyStatus>
DeviceGeolocation.requestTemporaryFullAccuracy({purposeKey}) Future<LocationAccuracyStatus>
DeviceGeolocation.openAppSettings() Future<bool>
DeviceGeolocation.openLocationSettings() Future<bool>
DeviceGeolocation.distanceBetween(lat1, lon1, lat2, lon2) double (meters)
DeviceGeolocation.bearingBetween(lat1, lon1, lat2, lon2) double (degrees)

Migration from flutter-geolocator #

geolocator device_geolocation
Geolocator.checkPermission() DeviceGeolocation.checkPermission()
Geolocator.requestPermission() DeviceGeolocation.requestPermission()
Geolocator.isLocationServiceEnabled() DeviceGeolocation.isLocationServiceEnabled()
Geolocator.getLastKnownPosition() DeviceGeolocation.getLastKnownPosition()
Geolocator.getCurrentPosition() DeviceGeolocation.getCurrentPosition()
Geolocator.getPositionStream() DeviceGeolocation.getPositionStream()
Geolocator.openAppSettings() DeviceGeolocation.openAppSettings()
Geolocator.openLocationSettings() DeviceGeolocation.openLocationSettings()
Geolocator.distanceBetween(...) DeviceGeolocation.distanceBetween(...)
Geolocator.bearingBetween(...) DeviceGeolocation.bearingBetween(...)

Asking for background-location permission on Android 10+ collapses into a single call:

// Was (geolocator): two manual calls
// Now (device_geolocation):
await DeviceGeolocation.requestPermission(requestBackground: true);

Testing your app with the bundled mock #

This package ships a ready-to-use mock so you can unit/widget-test screens that depend on the location API without reaching the platform channel.

import 'package:device_geolocation/device_geolocation.dart';
import 'package:device_geolocation/testing.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  late DeviceGeolocationMock mock;

  setUp(() {
    mock = DeviceGeolocationMock.install();
  });

  test('shows the user\'s coordinates', () async {
    mock.setPosition(mock.makePosition(latitude: 41.4, longitude: 2.2));

    final p = await DeviceGeolocation.getCurrentPosition();
    expect(p.latitude, 41.4);
    expect(p.longitude, 2.2);
  });

  test('streams live updates', () async {
    final stream = DeviceGeolocation.getPositionStream();
    final future = stream.first;
    mock.emitPosition(mock.makePosition(latitude: 1, longitude: 2));
    expect((await future).latitude, 1);
  });

  test('simulates a denied permission', () async {
    mock.permission = LocationPermission.deniedForever;
    expect(
      await DeviceGeolocation.checkPermission(),
      LocationPermission.deniedForever,
    );
  });

  test('simulates a platform error', () async {
    mock.throwOnNext(PermissionDeniedException('denied'));
    expect(
      DeviceGeolocation.requestPermission(),
      throwsA(isA<PermissionDeniedException>()),
    );
  });
}

The mock also exposes lastRequestedBackground, lastForcedLocationManager, lastLocationSettings and lastPurposeKey for assertions, plus an async reset() to clear state and close stream controllers between tests.

Test coverage #

Run:

flutter test --coverage

The Dart library is covered by 53 tests. Current line coverage (measured locally; updated automatically in CI via Codecov):

Scope Coverage
Whole lib/ (incl. Linux backend) 63.8%
lib/ excluding the Linux D-Bus backend (*) 89.8%

(*) The Linux backend (lib/device_geolocation_linux.dart) talks to the GeoClue2 D-Bus service and can only be exercised end-to-end on a real Linux host with geoclue-2.0 installed; it is excluded from the headline coverage number.

License #

MIT. Portions inspired by flutter-geolocator by Baseflow.

1
likes
160
points
353
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Cross-platform Flutter geolocation plugin (Android, iOS, macOS, Web, Windows, Linux).

Repository (GitHub)
View/report issues

Topics

#geolocation #location #gps #geolocator

License

MIT (license)

Dependencies

dbus, flutter, flutter_web_plugins, meta, plugin_platform_interface, web

More

Packages that depend on device_geolocation

Packages that implement device_geolocation