If you need to use Location services in your app then this tutorial is for you. Here we will explore different Location examples using various plugins. These plugins make using such services easier and more convenient.
When we talk about Location services we mean for example getting current location of a device, getting last known location, getting continouys location updates, even checking if location services are enabled or supported.
Let's look at the solutions and examples available.
Location Services using Flutter Geolocator Plugin
This is a Flutter geolocation plugin which provides easy access to platform specific location services.
For example FusedLocationProviderClient or if not available the LocationManager on Android and CLLocationManager on iOS.
Through this package you can:
- Obtain the last known location;
- Obtain the current location of the device;
- Obtain continuous location updates;
- Check if location services are enabled on the device;
- Calculate the distance (in meters) between two geocoordinates;
- Calculate the bearing between two geocoordinates;
Let's see how to use this extension.
Step 1: Install it
Start by installing the plugin. In your pubspec.yaml
specify the plugin as a dependency:
dependencies:
geolocator: ^7.6.2
Then sync.
You can also install it via the terminal using the following statement:
flutter pub add geolocator
Step 2: Setup
For this package to work, you need to a small setup.
Android Setup
Step 2.1 : Use AndroidX
For Android you need to use AndroidX. Here is how you do that:
- Add the following to your "gradle.properties" file:
android.useAndroidX=true
android.enableJetifier=true
- Make sure you set the
compileSdkVersion
in your "android/app/build.gradle" file to 30:
android {
compileSdkVersion 30
...
}
- Make sure you replace all the
android.
dependencies to their AndroidX counterparts (a full list can be found here: Migrating to AndroidX).
Step 2.2: Add Permissions
There are location permission that you have to add in your AndroidManifest.xml
file. Either the ACCESS_COARSE_LOCATION
or the ACCESS_FINE_LOCATION
.
Just open the AndroidManifest.xml
file inside the android/app/src/main
directory and add one of the following two lines as direct children of the <manifest>
tag (when you configure both permissions the ACCESS_FINE_LOCATION
will be used by the geolocator plugin):
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Starting from Android 10 you need to add the
ACCESS_BACKGROUND_LOCATION
permission (next to theACCESS_COARSE_LOCATION
or theACCESS_FINE_LOCATION
permission) if you want to continue receiving updates even when your App is running in the background (note that the geolocator plugin doesn't support receiving an processing location updates while running in the background):
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
iOS Setup
From inside the ios/Runner
directory, edit the Info.plist
file and add the following:
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs access to location when open.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs access to location when in the background.</string>
If you would like to receive updates when your App is in the background, you'll also need to add the Background Modes capability to your XCode project (Project > Signing and Capabilities > "+ Capability" button) and select Location Updates. Be careful with this, you will need to explain in detail to Apple why your App needs this when submitting your App to the AppStore. If Apple isn't satisfied with the explanation your App will be rejected.
When using the requestTemporaryFullAccuracy({purposeKey: "YourPurposeKey"})
method, a dictionary should be added to the Info.plist file.
<key>NSLocationTemporaryUsageDescriptionDictionary</key>
<dict>
<key>YourPurposeKey</key>
<string>The example App requires temporary access to the device's precise location.</string>
</dict>
The second key (in this example called YourPurposeKey
) should match the purposeKey that is passed in the requestTemporaryFullAccuracy()
method. It is possible to define multiple keys for different features in your app. More information can be found in Apple's documentation.
Step 3: Write Code
Let's look at some simple HowTo examples:
How to Get the Current Position
You can easily get the current location of the device using the function: getCurrentPosition()
. You can pass the appropriate parameters to this function. Below are the applicable parameters:
desiredAccuracy
: the accuracy of the location data that your app wants to receive;timeLimit
: the maximum amount of time allowed to acquire the current location. When the time limit is passed aTimeOutException
will be thrown and the call will be cancelled. By default no limit is configured.
Here is the code sample:
import 'package:geolocator/geolocator.dart';
Position position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
How to Get the Last Known User Position
The function getLastKnownPosition()
will give you the last known position of the device.
Here is the example code:
import 'package:geolocator/geolocator.dart';
Position position = await Geolocator.getLastKnownPosition();
How to Get Constant Position Updates
To get constant location change updates you can use the function getPositionStream()
. Here is the example code of how to use it:
import 'package:geolocator/geolocator.dart';
StreamSubscription<Position> positionStream = Geolocator.getPositionStream(locationOptions).listen(
(Position position) {
print(position == null ? 'Unknown' : position.latitude.toString() + ', ' + position.longitude.toString());
});
Here are the applicable parameters:
* <code>desiredAccuracy
: the accuracy of the location data that your app wants to receive; *distanceFilter
: the minimum distance (measured in meters) a device must move horizontally before an update event is generated; *timeInterval
: (Android only) the minimum amount of time that needs to pass before an update event is generated; *timeLimit
: the maximum amount of time allowed between location updates. When the time limit is passed aTimeOutException
will be thrown and the stream will be cancelled. By default no limit is configured.
How to Manually Check for Permissions
Even though this package will automatically request for permissions when you try to use it, you can also manually check and request for permissions by yourself.
For example to check if the user has been granted the permission for using Location services, use the function checkPermission()
:
import 'package:geolocator/geolocator.dart';
LocationPermission permission = await Geolocator.checkPermission();
Then let's say you have not been granted yet and you want to request it, use the requestPermission()
function:
import 'package:geolocator/geolocator.dart';
LocationPermission permission = await Geolocator.requestPermission();
How to Open Settings Page
If you want to navigate the user to the settings page so he/she can update the Location settings, then use the following code:
import 'package:geolocator/geolocator.dart';
await Geolocator.openAppSettings();
await Geolocator.openLocationSettings();
How to Get Distance Between Two geocordinates
use the following code:
import 'package:geolocator/geolocator.dart';
double distanceInMeters = Geolocator.distanceBetween(52.2165157, 6.9437819, 52.3546274, 4.8285838);
Full Example
Here is a full example. After installing the package and setting up your project as we had described, copy this code into your main.dart file and run:
main.dart
import 'dart:async';
import 'dart:io' show Platform;
import 'package:baseflow_plugin_template/baseflow_plugin_template.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
/// Defines the main theme color.
final MaterialColor themeMaterialColor =
BaseflowPluginExample.createMaterialColor(
const Color.fromRGBO(48, 49, 60, 1));
void main() {
runApp(const GeolocatorWidget());
}
/// Example [Widget] showing the functionalities of the geolocator plugin
class GeolocatorWidget extends StatefulWidget {
/// Creates a new GeolocatorWidget.
const GeolocatorWidget({Key? key}) : super(key: key);
/// Utility method to create a page with the Baseflow templating.
static ExamplePage createPage() {
return ExamplePage(
Icons.location_on, (context) => const GeolocatorWidget());
}
@override
_GeolocatorWidgetState createState() => _GeolocatorWidgetState();
}
class _GeolocatorWidgetState extends State<GeolocatorWidget> {
static const String _kLocationServicesDisabledMessage =
'Location services are disabled.';
static const String _kPermissionDeniedMessage = 'Permission denied.';
static const String _kPermissionDeniedForeverMessage =
'Permission denied forever.';
static const String _kPermissionGrantedMessage = 'Permission granted.';
final GeolocatorPlatform _geolocatorPlatform = GeolocatorPlatform.instance;
final List<_PositionItem> _positionItems = <_PositionItem>[];
StreamSubscription<Position>? _positionStreamSubscription;
StreamSubscription<ServiceStatus>? _serviceStatusStreamSubscription;
bool positionStreamStarted = false;
@override
void initState() {
super.initState();
_toggleServiceStatusStream();
}
PopupMenuButton _createActions() {
return PopupMenuButton(
elevation: 40,
onSelected: (value) async {
switch (value) {
case 1:
_getLocationAccuracy();
break;
case 2:
_requestTemporaryFullAccuracy();
break;
case 3:
_openAppSettings();
break;
case 4:
_openLocationSettings();
break;
case 5:
setState(_positionItems.clear);
break;
default:
break;
}
},
itemBuilder: (context) => [
if (Platform.isIOS)
const PopupMenuItem(
child: Text("Get Location Accuracy"),
value: 1,
),
if (Platform.isIOS)
const PopupMenuItem(
child: Text("Request Temporary Full Accuracy"),
value: 2,
),
const PopupMenuItem(
child: Text("Open App Settings"),
value: 3,
),
if (Platform.isAndroid)
const PopupMenuItem(
child: Text("Open Location Settings"),
value: 4,
),
const PopupMenuItem(
child: Text("Clear"),
value: 5,
),
],
);
}
@override
Widget build(BuildContext context) {
const sizedBox = SizedBox(
height: 10,
);
return BaseflowPluginExample(
pluginName: 'Geolocator',
githubURL: 'https://github.com/Baseflow/flutter-geolocator',
pubDevURL: 'https://pub.dev/packages/geolocator',
appBarActions: [
_createActions()
],
pages: [
ExamplePage(
Icons.location_on,
(context) => Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: ListView.builder(
itemCount: _positionItems.length,
itemBuilder: (context, index) {
final positionItem = _positionItems[index];
if (positionItem.type == _PositionItemType.log) {
return ListTile(
title: Text(positionItem.displayValue,
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
)),
);
} else {
return Card(
child: ListTile(
tileColor: themeMaterialColor,
title: Text(
positionItem.displayValue,
style: const TextStyle(color: Colors.white),
),
),
);
}
},
),
floatingActionButton: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
child: (_positionStreamSubscription == null ||
_positionStreamSubscription!.isPaused)
? const Icon(Icons.play_arrow)
: const Icon(Icons.pause),
onPressed: () {
positionStreamStarted = !positionStreamStarted;
_toggleListening();
},
tooltip: (_positionStreamSubscription == null)
? 'Start position updates'
: _positionStreamSubscription!.isPaused
? 'Resume'
: 'Pause',
backgroundColor: _determineButtonColor(),
),
sizedBox,
FloatingActionButton(
child: const Icon(Icons.my_location),
onPressed: _getCurrentPosition,
),
sizedBox,
FloatingActionButton(
child: const Icon(Icons.bookmark),
onPressed: _getLastKnownPosition,
),
],
),
),
)
]);
}
Future<void> _getCurrentPosition() async {
final hasPermission = await _handlePermission();
if (!hasPermission) {
return;
}
final position = await _geolocatorPlatform.getCurrentPosition();
_updatePositionList(
_PositionItemType.position,
position.toString(),
);
}
Future<bool> _handlePermission() async {
bool serviceEnabled;
LocationPermission permission;
// Test if location services are enabled.
serviceEnabled = await _geolocatorPlatform.isLocationServiceEnabled();
if (!serviceEnabled) {
// Location services are not enabled don't continue
// accessing the position and request users of the
// App to enable the location services.
_updatePositionList(
_PositionItemType.log,
_kLocationServicesDisabledMessage,
);
return false;
}
permission = await _geolocatorPlatform.checkPermission();
if (permission == LocationPermission.denied) {
permission = await _geolocatorPlatform.requestPermission();
if (permission == LocationPermission.denied) {
// Permissions are denied, next time you could try
// requesting permissions again (this is also where
// Android's shouldShowRequestPermissionRationale
// returned true. According to Android guidelines
// your App should show an explanatory UI now.
_updatePositionList(
_PositionItemType.log,
_kPermissionDeniedMessage,
);
return false;
}
}
if (permission == LocationPermission.deniedForever) {
// Permissions are denied forever, handle appropriately.
_updatePositionList(
_PositionItemType.log,
_kPermissionDeniedForeverMessage,
);
return false;
}
// When we reach here, permissions are granted and we can
// continue accessing the position of the device.
_updatePositionList(
_PositionItemType.log,
_kPermissionGrantedMessage,
);
return true;
}
void _updatePositionList(_PositionItemType type, String displayValue) {
_positionItems.add(_PositionItem(type, displayValue));
setState(() {});
}
bool _isListening() => !(_positionStreamSubscription == null ||
_positionStreamSubscription!.isPaused);
Color _determineButtonColor() {
return _isListening() ? Colors.green : Colors.red;
}
void _toggleServiceStatusStream() {
if (_serviceStatusStreamSubscription == null) {
final serviceStatusStream = _geolocatorPlatform.getServiceStatusStream();
_serviceStatusStreamSubscription =
serviceStatusStream.handleError((error) {
_serviceStatusStreamSubscription?.cancel();
_serviceStatusStreamSubscription = null;
}).listen((serviceStatus) {
String serviceStatusValue;
if (serviceStatus == ServiceStatus.enabled) {
if (positionStreamStarted) {
_toggleListening();
}
serviceStatusValue = 'enabled';
} else {
if (_positionStreamSubscription != null) {
setState(() {
_positionStreamSubscription?.cancel();
_positionStreamSubscription = null;
_updatePositionList(
_PositionItemType.log, 'Position Stream has been canceled');
});
}
serviceStatusValue = 'disabled';
}
_updatePositionList(
_PositionItemType.log,
'Location service has been $serviceStatusValue',
);
});
}
}
void _toggleListening() {
if (_positionStreamSubscription == null) {
final positionStream = _geolocatorPlatform.getPositionStream();
_positionStreamSubscription = positionStream.handleError((error) {
_positionStreamSubscription?.cancel();
_positionStreamSubscription = null;
}).listen((position) => _updatePositionList(
_PositionItemType.position,
position.toString(),
));
_positionStreamSubscription?.pause();
}
setState(() {
if (_positionStreamSubscription == null) {
return;
}
String statusDisplayValue;
if (_positionStreamSubscription!.isPaused) {
_positionStreamSubscription!.resume();
statusDisplayValue = 'resumed';
} else {
_positionStreamSubscription!.pause();
statusDisplayValue = 'paused';
}
_updatePositionList(
_PositionItemType.log,
'Listening for position updates $statusDisplayValue',
);
});
}
@override
void dispose() {
if (_positionStreamSubscription != null) {
_positionStreamSubscription!.cancel();
_positionStreamSubscription = null;
}
super.dispose();
}
void _getLastKnownPosition() async {
final position = await _geolocatorPlatform.getLastKnownPosition();
if (position != null) {
_updatePositionList(
_PositionItemType.position,
position.toString(),
);
} else {
_updatePositionList(
_PositionItemType.log,
'No last known position available',
);
}
}
void _getLocationAccuracy() async {
final status = await _geolocatorPlatform.getLocationAccuracy();
_handleLocationAccuracyStatus(status);
}
void _requestTemporaryFullAccuracy() async {
final status = await _geolocatorPlatform.requestTemporaryFullAccuracy(
purposeKey: "TemporaryPreciseAccuracy",
);
_handleLocationAccuracyStatus(status);
}
void _handleLocationAccuracyStatus(LocationAccuracyStatus status) {
String locationAccuracyStatusValue;
if (status == LocationAccuracyStatus.precise) {
locationAccuracyStatusValue = 'Precise';
} else if (status == LocationAccuracyStatus.reduced) {
locationAccuracyStatusValue = 'Reduced';
} else {
locationAccuracyStatusValue = 'Unknown';
}
_updatePositionList(
_PositionItemType.log,
'$locationAccuracyStatusValue location accuracy granted.',
);
}
void _openAppSettings() async {
final opened = await _geolocatorPlatform.openAppSettings();
String displayValue;
if (opened) {
displayValue = 'Opened Application Settings.';
} else {
displayValue = 'Error opening Application Settings.';
}
_updatePositionList(
_PositionItemType.log,
displayValue,
);
}
void _openLocationSettings() async {
final opened = await _geolocatorPlatform.openLocationSettings();
String displayValue;
if (opened) {
displayValue = 'Opened Location Settings';
} else {
displayValue = 'Error opening Location Settings';
}
_updatePositionList(
_PositionItemType.log,
displayValue,
);
}
}
enum _PositionItemType {
log,
position,
}
class _PositionItem {
_PositionItem(this.type, this.displayValue);
final _PositionItemType type;
final String displayValue;
}
Reference
Below are the code references
Number | Link |
---|---|
1. | Download code |
2. | Read more |