Using the geolocator Package to Access Device Location in Flutter

Location-based services have become a ubiquitous feature in modern mobile applications. Flutter, with its rich set of packages and cross-platform capabilities, makes it relatively straightforward to access a device’s location. The geolocator package is one of the most popular choices for achieving this. In this comprehensive guide, we’ll walk you through the process of using the geolocator package to access a device’s location in a Flutter app, ensuring accuracy, handling permissions, and providing best practices.

What is the Geolocator Package?

The geolocator package is a Flutter plugin that provides easy access to location services on both Android and iOS platforms. It abstracts the complexities of the native location APIs, offering a simple, unified interface for Flutter developers.

Why Use the Geolocator Package?

  • Cross-Platform Compatibility: Works seamlessly on both Android and iOS.
  • Simple API: Easy-to-use methods for fetching location data.
  • Accuracy Control: Allows configuring the desired accuracy level.
  • Permission Handling: Provides utilities for requesting and checking location permissions.
  • Background Location Updates: Supports background location updates (with appropriate configuration).

Step-by-Step Guide to Using Geolocator

Step 1: Add the Geolocator Package

First, you need to add the geolocator package to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  geolocator: ^10.1.1

After adding the dependency, run flutter pub get in your terminal to fetch the package.

Step 2: Configure Permissions

Before you can access the device’s location, you need to configure the necessary permissions for both Android and iOS.

Android Configuration
  1. Add Permissions to AndroidManifest.xml:

    Open android/app/src/main/AndroidManifest.xml and add the following permissions inside the <manifest> tag:

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    
    <!-- Required only when requesting background location access on Android 11+ -->
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" android:maxSdkVersion="30"/>
    
    <!-- Required only when targeting Android 12+ -->
    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="31"/>
    
    <!-- Required for foreground services on Android 9+ -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
  2. Handle Background Location (Optional):

    If your app needs to access the location in the background, you also need to declare a service in the AndroidManifest.xml:

    <application ...>
        <service
            android:name="com.baseflow.geolocator.GeolocatorService"
            android:foregroundServiceType="location"
            android:enabled="true"
            android:exported="false"/>
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
iOS Configuration
  1. Add Permissions to Info.plist:

    Open ios/Runner/Info.plist and add the following keys:

    <key>NSLocationWhenInUseUsageDescription</key>
    <string>This app needs access to your location when open to provide location-based services.</string>
    <key>NSLocationAlwaysUsageDescription</key>
    <string>This app needs access to your location even when in the background to provide enhanced location-based services.</string>
    <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
    <string>This app needs access to your location both when in use and in the background.</string>

    Note: NSLocationAlwaysUsageDescription and NSLocationAlwaysAndWhenInUseUsageDescription are required only if you need background location access.

Step 3: Import the Geolocator Package

In your Flutter Dart file, import the geolocator package:

import 'package:geolocator/geolocator.dart';

Step 4: Implement Location Logic

Now, let’s implement the core logic for checking permissions and fetching the location.

Checking Location Permissions

First, check if the user has granted location permissions. If not, request them:

import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';

class LocationService {
  Future<bool> handleLocationPermission(BuildContext context) async {
    bool serviceEnabled;
    LocationPermission permission;

    // Check if location services are enabled
    serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
          content: Text(
              'Location services are disabled. Please enable the services')));
      return false;
    }

    // Check location permission
    permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      permission = await Geolocator.requestPermission();
      if (permission == LocationPermission.denied) {
        ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('Location permissions are denied')));
        return false;
      }
    }

    if (permission == LocationPermission.deniedForever) {
      ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
          content: Text(
              'Location permissions are permanently denied, we cannot request permissions.')));
      return false;
    }

    // Permissions are granted
    return true;
  }
}
Fetching Current Location

Once permissions are granted, you can fetch the current location:

import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';

class LocationService {
  Future<Position> getCurrentLocation(BuildContext context) async {
    final hasPermission = await handleLocationPermission(context);
    if (!hasPermission) {
      return Future.error('Location permissions not granted');
    }

    try {
      return await Geolocator.getCurrentPosition(
          desiredAccuracy: LocationAccuracy.high);
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error getting location: ${e.toString()}')),
      );
      return Future.error(e);
    }
  }

  Future<bool> handleLocationPermission(BuildContext context) async {
      // Check if location services are enabled
      bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
      if (!serviceEnabled) {
          ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
              content: Text(
                  'Location services are disabled. Please enable the services')));
          return false;
      }

      // Check location permission
      LocationPermission permission = await Geolocator.checkPermission();
      if (permission == LocationPermission.denied) {
          permission = await Geolocator.requestPermission();
          if (permission == LocationPermission.denied) {
              ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(content: Text('Location permissions are denied')));
              return false;
          }
      }

      if (permission == LocationPermission.deniedForever) {
          ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
              content: Text(
                  'Location permissions are permanently denied, we cannot request permissions.')));
          return false;
      }

      // Permissions are granted
      return true;
  }
}
Example Usage in a Widget

Here’s how you might use this service within a Flutter widget:

import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';

class LocationWidget extends StatefulWidget {
  @override
  _LocationWidgetState createState() => _LocationWidgetState();
}

class _LocationWidgetState extends State<LocationWidget> {
  String _locationText = 'Fetching location...';

  @override
  void initState() {
    super.initState();
    _getLocation();
  }

  Future<void> _getLocation() async {
    final locationService = LocationService();

    try {
      Position position = await locationService.getCurrentLocation(context);
      setState(() {
        _locationText = 'Latitude: ${position.latitude}, Longitude: ${position.longitude}';
      });
    } catch (e) {
      setState(() {
        _locationText = 'Error: ${e.toString()}';
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Location Example')),
      body: Center(
        child: Text(_locationText),
      ),
    );
  }
}

Best Practices for Using Geolocator

  • Handle Permissions Gracefully:

    Always check and request location permissions before attempting to fetch the location. Provide informative messages to the user about why the location permission is needed.

  • Configure Accuracy:

    Use the desiredAccuracy parameter of the getCurrentPosition method to balance accuracy with power consumption. High accuracy consumes more battery.

    Position position = await Geolocator.getCurrentPosition(
        desiredAccuracy: LocationAccuracy.high); // or balanced, low, lowest, etc.
  • Handle Errors:

    Wrap your location fetching logic in a try-catch block to handle potential exceptions (e.g., when location services are disabled or unavailable).

  • Use Streams for Continuous Updates:

    For apps that require continuous location updates, use the Geolocator.getPositionStream() method to listen to location changes over time. Don’t forget to cancel the stream subscription when it’s no longer needed to avoid memory leaks.

    StreamSubscription<Position> positionStream = Geolocator.getPositionStream(
        locationSettings: const LocationSettings(
            accuracy: LocationAccuracy.high,
            distanceFilter: 100  // Minimum distance (meters) a device must move horizontally before an update event is generated;
        )).listen((Position position) {
      print('New position: ${position.latitude}, ${position.longitude}');
    });
    
    // Cancel subscription when not needed
    @override
    void dispose() {
      positionStream.cancel();
      super.dispose();
    }
  • Consider Background Location Updates:

    If your app needs location updates even when it’s in the background, configure the appropriate permissions and use Geolocator.getPositionStream() with background location settings. Be mindful of user privacy and battery consumption when using background location.

Advanced Features

Distance Calculation

The geolocator package provides utilities to calculate the distance between two geographical coordinates. Use the Geolocator.distanceBetween() method:

double distanceInMeters = Geolocator.distanceBetween(
    latitude1, longitude1, latitude2, longitude2);

Bearing Calculation

You can also calculate the bearing (direction) between two geographical coordinates using Geolocator.bearingBetween():

double bearingInDegrees = Geolocator.bearingBetween(
    latitude1, longitude1, latitude2, longitude2);

Conclusion

Accessing a device’s location in Flutter using the geolocator package is straightforward, thanks to its simple and cross-platform API. By following the steps outlined in this guide and adhering to best practices, you can integrate location-based services into your Flutter applications efficiently and responsibly. Always prioritize user privacy, handle permissions gracefully, and optimize location settings for the best balance between accuracy and power consumption.