Discovering user location is a very common and powerful use case for mobile applications these days. If you have ever tried to implement locations in Android, you know how complex and confusing the boilerplate code can get.
This is not the same with Flutter though — it has a lot of amazing packages that abstract that boilerplate code out for you and makes implementing geolocations a dream. Another bright side is that you get the functionality on both Android and iOS.
Let’s take a quick look at what we are building today to gather location data:

This article walks you through two of the most popular and easy-to-use Flutter packages for geolocation.
Let’s start with location, a Flutter favorite package. This one is as simple as it gets. Just three simple steps and you will be able to get the current user location along with handling location permissions.
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
Let’s do a quick check of things we need before jumping right ahead:
That’s pretty much it!
Add the dependency to your pubspec.yaml file:
dependencies:
location: ^4.2.0
Since permissions are handled differently by Android and iOS, we will have to add them separately on each platform.
Add the following location permissions to AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
If you want to access the user’s location in the background as well, use the enableBackgroundMode({bool enable}) API before accessing location in the background and add background permission to the manifest file as well:
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
Add the following location permissions to Info.plist:
<key>NSLocationWhenInUseUsageDescription</key> <string>This app needs to access your location</string>
NSLocationWhenInUseUsageDescription is the only permission you need. This allows you to access background location as well, the only caveat being that a blue badge is shown in the status bar when the app accesses location while in the background. Unlike Android, where we added separate permission for accessing the user’s location in the background.
We need to check Location Service status and Permission status before requesting user location, which is easily done using these few lines of code:
Location location = new Location();
bool _serviceEnabled;
PermissionStatus _permissionGranted;
_serviceEnabled = await location.serviceEnabled();
if (!_serviceEnabled) {
_serviceEnabled = await location.requestService();
if (!_serviceEnabled) {
return null;
}
}
_permissionGranted = await location.hasPermission();
if (_permissionGranted == PermissionStatus.denied) {
_permissionGranted = await location.requestPermission();
if (_permissionGranted != PermissionStatus.granted) {
return null;
}
}
First, we create a Location() object that is provided by the location package, which in turn provides us with two useful methods. serviceEnabled() checks if the device location is enabled or the user has disabled it manually.
For the latter, we display a native prompt that allows users to enable location quickly by calling requestService() and then we check one more time if they then enabled it from the prompt.
Once we are sure that location service is enabled, the next step is to check if our app has the necessary permissions to use it by calling hasPermission() that, which returns the PermissionStatus.
PermissionStatus is an enum that can have one of these three values:
PermissionStatus.granted: the permission for location services has been grantedPermissionStatus.denied: the permission for location services has been deniedPermissionStatus.deniedForever: the permission for location services has been denied forever by the user. This applies only to iOS. No dialog will be displayed on requestPermission() in this caseIf the status is denied, we can display the system prompt requesting location permissions by calling requestPermission(). For status granted, we can access location right away, so we return a null.
Use location.enableBackgroundMode(enable: true) if you want to access user location in the background as well.
If location services are available and the user has granted location permission, then we can get user location with just two lines of code — nope, I am not kidding:
LocationData _locationData; _locationData = await location.getLocation();
The LocationData class provides the following location info:
class LocationData {
final double latitude; // Latitude, in degrees
final double longitude; // Longitude, in degrees
final double accuracy; // Estimated horizontal accuracy of this location, radial, in meters
final double altitude; // In meters above the WGS 84 reference ellipsoid
final double speed; // In meters/second
final double speedAccuracy; // In meters/second, always 0 on iOS
final double heading; // Heading is the horizontal direction of travel of this device, in degrees
final double time; // timestamp of the LocationData
final bool isMock; // Is the location currently mocked
}
You can also get continuous callbacks by adding the onLocationChanged listener to listen to the location updates when the user location is changing, a very good use case for taxi apps, driver/rider apps, and so on:
location.onLocationChanged.listen((LocationData currentLocation) {
// current user location
});
N.B., don’t forget to cancel the stream subscription once you want to stop listening to the updates.
Voila! Now we have the current latitude and longitude values for user location.
Let’s utilize these latitude and longitude values to fetch the user’s complete address or reverse geocode.
For this, we will use another amazing Flutter package: geocode.
Add the dependency to your pubspec.yaml file:
dependencies:
geocode: 1.0.1
Getting an address couldn’t be easier. Just call reverseGeocoding(latitude: lat, longitude: lang). That’s it! A complete function with null checks looks like this:
Future<String> _getAddress(double? lat, double? lang) async {
if (lat == null || lang == null) return "";
GeoCode geoCode = GeoCode();
Address address =
await geoCode.reverseGeocoding(latitude: lat, longitude: lang);
return "${address.streetAddress}, ${address.city}, ${address.countryName}, ${address.postal}";
}
Wasn’t that simple!
The complete code looks like this:
class GetUserLocation extends StatefulWidget {
GetUserLocation({Key? key, required this.title}) : super(key: key);
final String title;
@override
_GetUserLocationState createState() => _GetUserLocationState();
}
class _GetUserLocationState extends State<GetUserLocation> {
LocationData? currentLocation;
String address = "";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
if (currentLocation != null)
Text(
"Location: ${currentLocation?.latitude}, ${currentLocation?.longitude}"),
if (currentLocation != null) Text("Address: $address"),
MaterialButton(
onPressed: () {
_getLocation().then((value) {
LocationData? location = value;
_getAddress(location?.latitude, location?.longitude)
.then((value) {
setState(() {
currentLocation = location;
address = value;
});
});
});
},
color: Colors.purple,
child: Text(
"Get Location",
style: TextStyle(color: Colors.white),
),
),
],
),
),
),
);
}
Future<LocationData?> _getLocation() async {
Location location = new Location();
LocationData _locationData;
bool _serviceEnabled;
PermissionStatus _permissionGranted;
_serviceEnabled = await location.serviceEnabled();
if (!_serviceEnabled) {
_serviceEnabled = await location.requestService();
if (!_serviceEnabled) {
return null;
}
}
_permissionGranted = await location.hasPermission();
if (_permissionGranted == PermissionStatus.denied) {
_permissionGranted = await location.requestPermission();
if (_permissionGranted != PermissionStatus.granted) {
return null;
}
}
_locationData = await location.getLocation();
return _locationData;
}
Future<String> _getAddress(double? lat, double? lang) async {
if (lat == null || lang == null) return "";
GeoCode geoCode = GeoCode();
Address address =
await geoCode.reverseGeocoding(latitude: lat, longitude: lang);
return "${address.streetAddress}, ${address.city}, ${address.countryName}, ${address.postal}";
}
}
Even though these packages have made our lives easier and we don’t have to deal with the complex process of accessing locations natively in Android and iOS, there are quite a few issues that you might face. Let’s take a look at them and the steps that can help you remediate these issues:
requestPermisssions() will not show the native prompt asking for location permissions. Make sure to handle this edge caseBecause accessing locations has been simplified by Flutter, it might be tempting for us as devs to add it to our app right away. But at the same time, we need to make sure that our app actually fits the use case for requesting user location and utilizing it to add some value for the user, rather than just sending location data to the server.
With the increase in security and privacy in the upcoming OS versions for both Android and iOS, accessing location data without providing a value to the user might get your app rejected from the stores. There are a lot of good use cases where you can use user location, e.g., personalizing home screen based on user location for a food/delivery app that displays restaurants ordered by proximity to the user’s current location. A pickup/delivery app is the most common use case.
You can also ask for user location on a specific screen where you actually want to use it, rather than asking it right away on the home screen. This makes it clearer to the user, and they are less likely to deny location permissions.
Thanks for sticking around, happy coding fella! You can access the sample app used in the article here on GitHub.
Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, not
server-side
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
// Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>

Vibe coding isn’t just AI-assisted chaos. Here’s how to avoid insecure, unreadable code and turn your “vibes” into real developer productivity.

GitHub SpecKit brings structure to AI-assisted coding with a spec-driven workflow. Learn how to build a consistent, React-based project guided by clear specs and plans.

:has(), with examplesThe CSS :has() pseudo-class is a powerful new feature that lets you style parents, siblings, and more – writing cleaner, more dynamic CSS with less JavaScript.

Kombai AI converts Figma designs into clean, responsive frontend code. It helps developers build production-ready UIs faster while keeping design accuracy and code quality intact.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now