Getting Current Location in Flutter
Getting the location of a device can be tricky, maybe the location service is off, or perhaps the user has not granted yet permission for the app to access it. In this story, we are going to learn an approach to get the current device location and how to ask the user to enable location service and to provide permission to our app.
Enabling location service and asking for permission
Before trying to get the location, we need to make sure location service is enabled and location permission is provided. Of course, we will use a package for this task: Location Package.
In order to organize our code, we can create a new class called LocationHelper (is just a suggestion, you can call it whatever you want):
import 'package:location/location.dart';class LocationHelper {}
We are going to encapsulate all our location-related functions inside this class. Once we included the package in our app and imported the Location class in our library, let’s see how to check if location service is enabled:
Future<bool> _serviceEnabled() async {
Location location = Location();
bool serviceEnabled = await location.serviceEnabled();
if(!serviceEnabled) {
return await location.requestService();
} else {
return true;
}
}
In the previous code snippet we are checking if location service is enabled, and if it is not, we call location.requestService() to ask for the user to activate it. This will show a system dialog requesting the activation of the location service. If the user enables the service, the method will return true, if not, it will return false and we should handle that denial somehow, but first, let’s see how to check if the app has permission to access the location:
Future<bool> _hasPermission() async {
Location location = Location();
PermissionStatus permissionStatus = await location.hasPermission();
if(permissionStatus != PermissionStatus.granted
&& permissionStatus != PermissionStatus.grantedLimited) {
permissionStatus = await location.requestPermission();
return (permissionStatus == PermissionStatus.granted
|| permissionStatus == PermissionStatus.grantedLimited);
} else {
return true;
}
}
Here we are getting the permission status by calling location.hasPermission(), then if the permissionStatus is neither “granted” nor “grantedLimited", we request for the user permission by calling location.requestPermission(). If the user provides permission, the method will return true and we can proceed with obtaining the location, if not we should handle that denial again.
Handling denials
So, if the user doesn’t enable location service, or doesn’t provide permission, and getting the location is really necessary for our app functionality, what we can do then? One approach I like is to just gently insist. We can show our own dialogs to asking the user to enable the service or grant permission. For these cases when I need to show a dialog that has a specific message and a specific purpose, I like to use a widget like this:
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class ErrorMessage extends StatelessWidget {
final String text;
final Function? callback;
final String? buttonText;
ErrorMessage({required this.text, this.callback,
this.buttonText});
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Icon(
Icons.error_outline,
color: Theme.of(context).primaryColor,
size: 48,
),
content: Text(text),
actions: [
TextButton(
child: Text(
buttonText != null? buttonText! : "Ok",
style: Theme.of(context).textTheme.headline6,
),
onPressed: () {
Navigator.pop(context);
if(callback != null) {
callback!.call();
}
},
)
],
);
}
}
As you can see, it returns an AlertDialog that receives as parameters a text (the message), an optional callback that will be called when the button is pressed, and an optional text for the button.
With this widget, we can show our dialogs like this:
showDialog(context: context, builder: (BuildContext context) {
return ErrorMessage(
text: 'Do the thing I need you to do... please',
callback: () {
operationWeNeedToPerform();
},
buttonText: 'Ok, ok',
);
});
The callback can be any function we need to perform when the user presses the dialog button.
And with that said, we can combine the previous code in a method that will ask for location service enabling, ask for providing permission, and execute an errorCallback in case of user denial:
Future<bool> enableAndGrantPermissionToLocation(BuildContext context,
Function errorCallback) async {
bool hasPermission = false;
bool serviceEnabled = await this._serviceEnabled();
if(!serviceEnabled) {
showDialog(context: context, builder: (BuildContext context) {
return ErrorMessage(
text: 'Please enable location services on your device',
callback: () {
errorCallback.call();
},
buttonText: 'Done',
);
});
} else {
hasPermission = await this._hasPermission();
if(!hasPermission) {
showDialog(context: context, builder: (BuildContext context) {
return ErrorMessage(
text: 'Please grant permission to access to your current location',
callback: () {
errorCallback.call();
}
);
});
}
}
return serviceEnabled && hasPermission;
}
Getting the current location
So far, we know how to make sure location service is enabled and location permission is provided, so it is time to get the current location, and this is as simple as calling one line of code:
await Location().getLocation();
This method gets the current location of the user, but, it throws an error if the app has no permission, that is why we ask for the user permission in the first place. And, to keep our code organized, we will include it in a method inside our LocationHelper class:
Future<LocationData> getCurrentLocation(BuildContext context) async {
return await Location().getLocation();
}
Now we are ready to use our helper from the widget where we need the location:
getCurrentGPSLocation() async {
LocationHelper locationHelper = LocationHelper();
bool canGetLocation = await locationHelper
.enableAndGrantPermissionToLocation(context, getCurrentGPSLocation);
if(canGetLocation) {
LoadingIndicatorDialog().show(context, text: 'Getting current location...');
_myLocation = await locationHelper.getCurrentLocation(context);
LoadingIndicatorDialog().dismiss();
}
}
This method will try to get the current location and assign it to a variable called _myLocation. As you can see, first we call our helper method enableAndGrantPermissionToLocation() to make sure location service and permission is available, and if you look at its parameters, you will find that we are using our getCurrentGPSLocation() method as the errorCallback for the ErrorMessage that we show in case of user denial. In this way, if the user ignores our requests to enable location service or providing permission, the app will try again. Then, if canGetLocation is true we get the location by calling locationHelper.getCurrentLocation().
Btw, if you implement this code you will get a compiling error in these lines:
LoadingIndicatorDialog().show(context, text: 'Getting current location...');
...
LoadingIndicatorDialog().dismiss();
This is because LoadingIndicatorDialog is a widget I usually implement in my projects when I need to show a Progress Indicator during an asynchronous operation. Is a good idea to show a progress indicator in this case because the process to get the current location can take a few seconds and we do want to give feedback to our users by indicating that something is being processed. If you want to learn how to show a Progress Indicator during asynchronous operations, go to this story: Showing a Progress Indicator easily in Flutter.
And that is it! We have obtained the current location safely without errors. Hope this information is helpful for you. And if you use a different approach to get the location, please let me know in the comments. See you in another story.