Handling Permissions Required to Access Location Data in Flutter

Accessing location data is a common requirement in modern mobile applications. Whether it’s for providing location-based services, mapping features, or geofencing capabilities, properly handling location permissions is crucial. In Flutter, managing these permissions requires a clear understanding of the platform-specific implementations for both Android and iOS.

Why Handle Location Permissions?

Respecting user privacy is paramount. Users need to have control over which applications can access their location. Properly requesting and handling location permissions not only ensures compliance with privacy policies but also builds trust with your users.

Steps to Handle Location Permissions in Flutter

Here’s a step-by-step guide to handling location permissions in Flutter:

Step 1: Add Dependencies

Add the necessary dependencies to your pubspec.yaml file. You’ll need the permission_handler package for managing permissions and the geolocator package for accessing location data.

dependencies:
  flutter:
    sdk: flutter
  permission_handler: ^11.3.0
  geolocator: ^11.0.0

Run flutter pub get to install the packages.

Step 2: Configure Native Platforms

You need to configure the Android and iOS platforms to request location permissions.

Android Configuration

Add the following permission to your AndroidManifest.xml file (android/app/src/main/AndroidManifest.xml):







    
    ...

Note:

  • ACCESS_FINE_LOCATION provides precise location.
  • ACCESS_COARSE_LOCATION provides an approximate location.
  • ACCESS_BACKGROUND_LOCATION is needed for accessing location in the background (required for Android 10 and later). You must also have the FOREGROUND_SERVICE permission.

Add the necessary explanation for location permission usage. Open android/app/src/main/res/values/strings.xml and add:

Your App Name
This app needs location permission to provide location-based services.

Create an API key for Google Maps. Refer to Google Maps Platform documentation. Put the created api key in place of YOUR_GOOGLE_MAPS_API_KEY in the AndroidManifest.xml file.

iOS Configuration

Add the following keys to your Info.plist file (ios/Runner/Info.plist):

NSLocationWhenInUseUsageDescription
This app needs access to your location when the app is open to provide location-based services.
NSLocationAlwaysUsageDescription
This app needs access to your location, even when in the background, to provide location-based services.
NSLocationAlwaysAndWhenInUseUsageDescription
This app needs access to your location, both when the app is open and in the background, to provide location-based services.

Note:

  • NSLocationWhenInUseUsageDescription is used when you need location access while the app is in use.
  • NSLocationAlwaysUsageDescription (deprecated, use NSLocationAlwaysAndWhenInUseUsageDescription) is used when you need background location access.
  • NSLocationAlwaysAndWhenInUseUsageDescription combines both and is required for iOS 11 and later to support background location access.

Step 3: Request Permissions in Flutter

Use the permission_handler package to request the necessary permissions in your Flutter code:

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

class LocationPermissionHandler {
  Future requestLocationPermission() async {
    final PermissionStatus status = await Permission.location.request();
    return status;
  }

  Future checkLocationPermission() async {
    final PermissionStatus status = await Permission.location.status;
    return status.isGranted;
  }
}

Example Flutter Widget for Permissions :

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

class LocationWidget extends StatefulWidget {
  const LocationWidget({super.key});

  @override
  State createState() => _LocationWidgetState();
}

class _LocationWidgetState extends State {
  String _locationStatus = 'Location permission not requested';
  String _locationData = 'No location data';

  Future _handleLocation() async {
    final locationPermissionHandler = LocationPermissionHandler();
    final permissionStatus = await locationPermissionHandler.requestLocationPermission();

    setState(() {
      _locationStatus = 'Permission Status: ${permissionStatus.toString()}';
    });

    if (permissionStatus.isGranted) {
      _getCurrentLocation();
    } else {
      setState(() {
        _locationData = 'Location permission denied.';
      });
    }
  }

  Future _getCurrentLocation() async {
    try {
      final position = await Geolocator.getCurrentPosition(
          desiredAccuracy: LocationAccuracy.high);
      setState(() {
        _locationData =
            'Latitude: ${position.latitude}, Longitude: ${position.longitude}';
      });
    } catch (e) {
      setState(() {
        _locationData = 'Error: ${e.toString()}';
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Text(_locationStatus),
          const SizedBox(height: 20),
          ElevatedButton(
            onPressed: _handleLocation,
            child: const Text('Get Location Permission'),
          ),
          const SizedBox(height: 20),
          Text(_locationData),
        ],
      ),
    );
  }
}

Call the function and handle permissions on Button Press:

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Location Permission Example'),
      ),
      body: Center(
        child: LocationWidget(),
      ),
    );
  }
}

Step 4: Handle Permission Status

It’s essential to handle different permission statuses and provide appropriate UI updates.

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

class PermissionHandler {
  Future handlePermission(BuildContext context) async {
    final status = await Permission.location.request();

    if (status.isGranted) {
      // Permission granted, proceed with location access
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Location permission granted')),
      );
    } else if (status.isDenied) {
      // Permission denied, show rationale and possibly navigate to settings
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Location permission denied')),
      );
    } else if (status.isPermanentlyDenied) {
      // Permission permanently denied, navigate to app settings
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Location permission permanently denied, please enable it in app settings')),
      );
      openAppSettings();
    }
  }
}

class PermissionScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final permissionHandler = PermissionHandler();
    return Scaffold(
      appBar: AppBar(
        title: Text('Permission Example'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            await permissionHandler.handlePermission(context);
          },
          child: Text('Request Location Permission'),
        ),
      ),
    );
  }
}

Step 5: Get Location Data

After obtaining permission, use the geolocator package to retrieve location data:

import 'package:geolocator/geolocator.dart';

Future getCurrentLocation() async {
  try {
    Position position = await Geolocator.getCurrentPosition(
      desiredAccuracy: LocationAccuracy.high,
    );
    return position;
  } catch (e) {
    print('Error getting location: ${e.toString()}');
    throw e; // Re-throw the exception for handling in the UI
  }
}

Call the function:

ElevatedButton(
      onPressed: () async {
        try {
          Position position = await getCurrentLocation();
          print('Latitude: ${position.latitude}, Longitude: ${position.longitude}');
        } catch (e) {
          print('Error: ${e.toString()}');
        }
      },
      child: Text('Get Current Location'),
    )

Step 6: Handle Background Location (If Required)

For accessing location in the background, additional steps are required.

Android

Ensure ACCESS_BACKGROUND_LOCATION permission is requested.

Future requestBackgroundLocationPermission() async {
    final status = await Permission.locationAlways.request(); //locationAlways is needed instead of location
    return status.isGranted;
}

You can get continuous background location updates by using Geolocator.getPositionStream().

import 'package:geolocator/geolocator.dart';

Stream getLocationStream() {
  const LocationSettings locationSettings = LocationSettings(
    accuracy: LocationAccuracy.high,
    distanceFilter: 100, // Get updates when the location changes by 100 meters.
  );

  return Geolocator.getPositionStream(locationSettings: locationSettings);
}

Invoke the above mentioned stream by a StreamBuilder,

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

class BackgroundLocationUpdates extends StatefulWidget {
  const BackgroundLocationUpdates({Key? key}) : super(key: key);

  @override
  State createState() => _BackgroundLocationUpdatesState();
}

class _BackgroundLocationUpdatesState extends State {
  StreamSubscription? _positionStreamSubscription;
  String _locationStatus = 'Getting location...';

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

  void _startListeningForLocationUpdates() {
    // Set up a stream subscription to listen for ongoing location updates.
    _positionStreamSubscription = Geolocator.getPositionStream(
        locationSettings: const LocationSettings(
      accuracy: LocationAccuracy.high,
      distanceFilter: 100, //Get updates when the location changes by 100 meters.
    )).listen((Position? position) {
      if (position != null) {
        setState(() {
          _locationStatus =
              'Lat: ${position.latitude}, Long: ${position.longitude} - time ${position.timestamp}';
        });
      } else {
        setState(() {
          _locationStatus = 'Null Received!';
        });
      }
    });
  }

  @override
  void dispose() {
    //It's very important to stop background services and streams
    //that are not in the Widget tree
    _positionStreamSubscription?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Foreground Geolocator stream'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            const Text('Continuously updated location'),
            const SizedBox(
              height: 20,
            ),
            Text(_locationStatus),
          ],
        ),
      ),
    );
  }
}
iOS

You need to enable the Background Modes capability in Xcode:

  1. Open ios/Runner/Runner.xcworkspace in Xcode.
  2. Select the Runner project in the Project navigator.
  3. Select the Runner target.
  4. Go to Signing & Capabilities.
  5. Click + Capability.
  6. Add Background Modes.
  7. Check Location updates.

Note: iOS requires significant justification for background location access due to its privacy implications. Your app may face rejection during the review process if background location usage isn’t well-justified.

Best Practices for Handling Location Permissions

  • Explain Why: Always provide a clear explanation to the user about why your app needs location access.
  • Request Strategically: Request permissions only when they are needed.
  • Graceful Degradation: Ensure that your app functions properly even if the user denies location permission.
  • Handle Errors: Properly handle scenarios where location data is unavailable.

Conclusion

Properly handling location permissions is essential for building robust and user-friendly Flutter applications that require location data. By following these steps, you can ensure compliance with platform-specific requirements, respect user privacy, and provide a seamless user experience.