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_LOCATIONprovides precise location.ACCESS_COARSE_LOCATIONprovides an approximate location.ACCESS_BACKGROUND_LOCATIONis 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:
NSLocationWhenInUseUsageDescriptionis used when you need location access while the app is in use.NSLocationAlwaysUsageDescription(deprecated, useNSLocationAlwaysAndWhenInUseUsageDescription) is used when you need background location access.NSLocationAlwaysAndWhenInUseUsageDescriptioncombines 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:
- Open
ios/Runner/Runner.xcworkspacein Xcode. - Select the
Runnerproject in the Project navigator. - Select the
Runnertarget. - Go to
Signing & Capabilities. - Click
+ Capability. - Add
Background Modes. - 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.