Getting Device Location with geolocator
Getting Device Location with geolocator
The geolocator package is the most widely used Flutter plugin for accessing the device's GPS and network-based positioning system. It provides a clean, cross-platform Dart API for both one-shot position requests and continuous location streams, returning a rich Position object that includes latitude, longitude, altitude, accuracy, speed, and heading.
AndroidManifest.xml and Info.plist.Package Setup
Add the dependency to pubspec.yaml and run flutter pub get:
pubspec.yaml
dependencies:
flutter:
sdk: flutter
geolocator: ^13.0.0
For Android, add the following permissions inside the <manifest> tag of android/app/src/main/AndroidManifest.xml:
AndroidManifest.xml permissions
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Only if you need background location -->
<!-- <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> -->
For iOS, add the following keys to ios/Runner/Info.plist:
Info.plist keys
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app uses your location to show nearby places.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs background location for tracking.</string>
Checking and Requesting Permissions
Before requesting position data you must verify that location services are enabled on the device and that your app has the necessary permission. The geolocator API provides dedicated helpers for both checks:
Permission check and request
import 'package:geolocator/geolocator.dart';
Future<void> _checkPermissions() async {
// 1. Is the location hardware/service enabled?
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
// Location services are turned off in device settings
throw Exception('Location services are disabled.');
}
// 2. Has the user granted permission to this app?
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
// First-time or after the user previously denied
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
throw Exception('Location permission denied.');
}
}
if (permission == LocationPermission.deniedForever) {
// User selected "Never" – must open app settings manually
throw Exception('Location permission permanently denied.');
}
// Permission is whileInUse or always – safe to proceed
}
One-Shot Position Request
Use Geolocator.getCurrentPosition() to fetch a single Position snapshot. This is ideal for features like "find nearby restaurants" where you only need the location once. You can tune accuracy vs battery cost with the LocationAccuracy enum:
LocationAccuracy.lowest— ~3 km accuracy, minimal batteryLocationAccuracy.low— ~1 km accuracyLocationAccuracy.medium— ~100 m accuracyLocationAccuracy.high— ~10 m accuracy (GPS on)LocationAccuracy.best— maximum accuracy, highest battery costLocationAccuracy.bestForNavigation— like best but also uses motion sensors
getCurrentPosition example
import 'package:geolocator/geolocator.dart';
class LocationService {
/// Returns the device's current position after checking permissions.
static Future<Position> determinePosition() async {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
return Future.error('Location services are disabled.');
}
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
return Future.error('Location permissions are denied');
}
}
if (permission == LocationPermission.deniedForever) {
return Future.error(
'Location permissions are permanently denied, '
'we cannot request permissions.',
);
}
// At this point permissions are granted and we can
// continue accessing the position of the device.
return await Geolocator.getCurrentPosition(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.high,
timeLimit: Duration(seconds: 15),
),
);
}
}
// Usage in a StatefulWidget
Future<void> _fetchLocation() async {
try {
final Position position = await LocationService.determinePosition();
print('Latitude: ${position.latitude}');
print('Longitude: ${position.longitude}');
print('Accuracy: ${position.accuracy} m');
print('Altitude: ${position.altitude} m');
print('Speed: ${position.speed} m/s');
} catch (e) {
print('Error: $e');
}
}
timeLimit on getCurrentPosition(). Without it the Future can hang indefinitely if the GPS cannot get a fix (e.g., indoors or in a tunnel). A 10–15 second timeout with a graceful fallback provides a much better user experience.Continuous Location Stream
Use Geolocator.getPositionStream() for real-time tracking use-cases such as navigation, fitness apps, or live delivery tracking. It returns a Stream<Position> that emits new values whenever the device moves beyond the configured distance filter or interval. Always cancel the stream subscription when the widget is disposed to avoid memory leaks:
getPositionStream with StreamSubscription
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
class LiveLocationWidget extends StatefulWidget {
const LiveLocationWidget({super.key});
@override
State<LiveLocationWidget> createState() => _LiveLocationWidgetState();
}
class _LiveLocationWidgetState extends State<LiveLocationWidget> {
StreamSubscription<Position>? _positionSubscription;
Position? _currentPosition;
String _statusMessage = 'Waiting for location...';
@override
void initState() {
super.initState();
_startLocationStream();
}
void _startLocationStream() {
const locationSettings = LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 10, // emit only when moved >= 10 metres
);
_positionSubscription =
Geolocator.getPositionStream(locationSettings: locationSettings)
.listen(
(Position position) {
setState(() {
_currentPosition = position;
_statusMessage =
'Lat: ${position.latitude.toStringAsFixed(6)}, '
'Lng: ${position.longitude.toStringAsFixed(6)}';
});
},
onError: (Object e) {
setState(() {
_statusMessage = 'Stream error: $e';
});
},
);
}
@override
void dispose() {
// Critical: cancel the subscription to stop GPS hardware
_positionSubscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_statusMessage, textAlign: TextAlign.center),
if (_currentPosition != null) ...[
Text('Accuracy: ${_currentPosition!.accuracy.toStringAsFixed(1)} m'),
Text('Altitude: ${_currentPosition!.altitude.toStringAsFixed(1)} m'),
Text('Speed: ${_currentPosition!.speed.toStringAsFixed(2)} m/s'),
],
],
);
}
}
_positionSubscription?.cancel() in dispose() keeps the GPS active after the widget is removed from the tree. This drains the battery and can cause setState calls on a disposed widget, leading to errors at runtime.The Position Object
Both getCurrentPosition() and getPositionStream() yield a Position object with the following key properties:
latitude/longitude— coordinates in decimal degrees (WGS-84)accuracy— estimated horizontal accuracy in metresaltitude— height above sea level in metresaltitudeAccuracy— estimated vertical accuracy in metres (iOS / Android 13+)speed— ground speed in metres per secondspeedAccuracy— estimated speed accuracy in metres per secondheading— direction of travel in degrees (0 = north, clockwise)timestamp—DateTimewhen the fix was obtained
Summary
The geolocator package gives you everything needed to work with device location in Flutter. Use isLocationServiceEnabled() and checkPermission() / requestPermission() before accessing any position data. Call getCurrentPosition() for one-shot fixes with a sensible timeLimit, and getPositionStream() with an appropriate distanceFilter for live tracking. Always cancel your StreamSubscription in dispose() to protect battery life and prevent widget lifecycle errors.