Handling User Permissions Required to Access Location Data on Different Platforms in Flutter

In Flutter development, accessing a user’s location is a common requirement for various types of applications, from navigation and mapping to location-based services. However, obtaining location data necessitates requesting user permissions, which differ slightly across Android and iOS platforms. Effectively managing these platform-specific requirements is crucial for creating a smooth user experience. This article explores how to handle user permissions for location data in Flutter, ensuring compatibility across different platforms.

Understanding Location Permissions

Location permissions are necessary to comply with user privacy standards. Users must explicitly grant permission for an app to access their location data. On both Android and iOS, there are varying levels of permission that can be requested, each with its implications for data access and user experience.

Android Permissions

  • ACCESS_FINE_LOCATION: Provides access to precise location using GPS, Wi-Fi, and cellular data.
  • ACCESS_COARSE_LOCATION: Offers access to approximate location, usually derived from Wi-Fi and cellular data.
  • ACCESS_BACKGROUND_LOCATION: (Android 10 and above) Allows access to location data even when the app is in the background.

iOS Permissions

  • NSLocationWhenInUseUsageDescription: Allows access to location only when the app is in use.
  • NSLocationAlwaysUsageDescription: (Deprecated, use NSLocationAlwaysAndWhenInUseUsageDescription) Previously allowed location access at all times.
  • NSLocationAlwaysAndWhenInUseUsageDescription: Allows access to location at all times, requires both when-in-use and always permissions to be described in the Info.plist.
  • NSLocationTemporaryUsageDescriptionDictionary: Allows requesting temporary precise location access.

Setting Up Flutter Project

Before implementing location permissions, ensure that your Flutter project is correctly set up. Begin by adding the permission_handler package to your pubspec.yaml file.


dependencies:
  flutter:
    sdk: flutter
  permission_handler: ^11.1.0

Install the package by running:


flutter pub get

Configuring Permissions in Native Platforms

Next, configure the necessary permissions in your native Android and iOS projects.

Android Configuration

Add the necessary permissions to your AndroidManifest.xml file located in android/app/src/main:


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.your_app">
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" android:maxSdkVersion="28"/>
    <uses-feature android:name="android.hardware.location.gps" android:required="false" />
    <application
        android:name="io.flutter.app.FlutterApplication"
        android:label="your_app"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <meta-data
                android:name="io.flutter.embedding.android.NormalTheme"
                android:resource="@style/NormalTheme"
                />
            <meta-data
                android:name="io.flutter.embedding.android.SplashScreenDrawable"
                android:resource="@drawable/launch_background"
                />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
</manifest>

For Android 10 (API level 29) and above, requesting background location permission requires additional steps.

iOS Configuration

Add the following keys to your Info.plist file located in ios/Runner:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
 <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 in the background to provide location-based services.</string>
 <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
 <string>This app needs access to your location when in the background to provide location-based services.</string>
 <key>LSRequiresIPhoneOS</key>
 <true/>
</dict>
</plist>

These descriptions are shown to the user when the app requests location permissions, explaining why the permissions are needed.

Requesting Location Permissions in Flutter

Using the permission_handler package, you can request location permissions in your Flutter code. Here’s a function to handle location permission requests:


import 'package:permission_handler/permission_handler.dart';

Future<void> requestLocationPermission() async {
  final status = await Permission.location.request();

  if (status == PermissionStatus.granted) {
    print('Location permission granted');
  } else if (status == PermissionStatus.denied) {
    print('Location permission denied');
  } else if (status == PermissionStatus.permanentlyDenied) {
    print('Location permission permanently denied');
    openAppSettings(); // Open app settings to allow the user to enable the permission manually
  }
}

In this function:

  • Permission.location.request() triggers the permission request dialog to the user.
  • The status variable returns the result of the permission request:
    • granted: Permission is granted.
    • denied: Permission is denied. You might want to show a rationale and re-request the permission.
    • permanentlyDenied: Permission is permanently denied. Direct the user to the app settings to enable the permission manually.
  • openAppSettings() opens the app settings page, allowing the user to manually enable the permission.

Checking Location Permission Status

Before requesting a permission, it’s a good practice to check its current status. Here’s how to check the location permission status:


Future<void> checkLocationPermissionStatus() async {
  final status = await Permission.location.status;

  if (status == PermissionStatus.granted) {
    print('Location permission already granted');
  } else if (status == PermissionStatus.denied) {
    print('Location permission is denied, request permission');
    requestLocationPermission();
  } else if (status == PermissionStatus.permanentlyDenied) {
    print('Location permission is permanently denied, open app settings');
    openAppSettings();
  }
}

Handling Background Location Permissions

For applications that require location access even when running in the background (e.g., geofencing apps), you’ll need to handle background location permissions differently.

Android

For Android 10 and higher, you need to request ACCESS_BACKGROUND_LOCATION. First, check if the permission is granted:


import 'package:permission_handler/permission_handler.dart';

Future<void> requestBackgroundLocationPermission() async {
  final status = await Permission.locationAlways.request();

  if (status == PermissionStatus.granted) {
    print('Background location permission granted');
  } else if (status == PermissionStatus.denied) {
    print('Background location permission denied');
  } else if (status == PermissionStatus.permanentlyDenied) {
    print('Background location permission permanently denied');
    openAppSettings();
  }
}

iOS

On iOS, background location access is managed via the NSLocationAlwaysAndWhenInUseUsageDescription key in the Info.plist. When requesting location permissions, the system automatically handles background access if the user grants it.

Platform-Specific Logic

Sometimes, you may need to execute platform-specific code. The dart:io library can be used to check the platform at runtime:


import 'dart:io' show Platform;

void checkPlatform() {
  if (Platform.isAndroid) {
    print('Running on Android');
  } else if (Platform.isIOS) {
    print('Running on iOS');
  }
}

Best Practices

  • Provide a Clear Rationale: Always explain to the user why your app needs location permissions before requesting them. This can be done using dialogs or informational screens.
  • Request Permissions Only When Needed: Request permissions only when you actually need the location data. For instance, wait until the user initiates an action that requires location.
  • Graceful Degradation: Ensure that your app functions gracefully if the user denies location permissions. Provide alternative options or limited functionality.
  • Test Thoroughly: Test location permissions on both Android and iOS devices to ensure proper behavior across different platforms and OS versions.

Example Flutter Code

Here’s a full example of a Flutter widget that handles location permissions:


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

class LocationPermissionHandler extends StatefulWidget {
  @override
  _LocationPermissionHandlerState createState() => _LocationPermissionHandlerState();
}

class _LocationPermissionHandlerState extends State<LocationPermissionHandler> {
  String _permissionStatus = 'Unknown';

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

  Future<void> _checkLocationPermissionStatus() async {
    final status = await Permission.location.status;
    setState(() {
      _permissionStatus = status.toString();
    });
  }

  Future<void> _requestLocationPermission() async {
    final status = await Permission.location.request();
    setState(() {
      _permissionStatus = status.toString();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Location Permission Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Permission Status: $_permissionStatus',
              style: TextStyle(fontSize: 16),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _requestLocationPermission,
              child: Text('Request Location Permission'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                openAppSettings();
              },
              child: Text('Open App Settings'),
            ),
          ],
        ),
      ),
    );
  }
}

Conclusion

Handling user permissions for location data on different platforms in Flutter requires a combination of configuring native platform settings and utilizing Flutter packages like permission_handler. By implementing best practices and providing clear rationale to users, you can create a seamless and privacy-conscious location-based experience in your Flutter applications. Testing across different Android and iOS versions is crucial to ensure that permission requests and handling work as expected, resulting in a robust and user-friendly app.