How to get the device's location in Flutter

Flutter Aug 10, 2023

Thinking about adding location features to your Flutter application? If you are building a navigation application, a running tracker, or anything that needs to use location data, the location package can make things much simpler. In this post, we will explore how to use this package to retrieve the device's location, handle permissions, and even keep track of ongoing location updates.

Installing

To get started, we need to install the location package into our project. The installation process is simple. Just execute the following command: flutter pub add location.

Once the command is executed, make sure to check your pubspec.yaml file for the added dependencies. You should see the location package included in the dependencies section, like this:

dependencies:
  flutter:
    sdk: flutter
  location: ^5.0.2+1

Android

In Android, to make this package work, we need to add two permission entries in the AndroidManifest.xml file:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

iOS

In iOS, you need to add the location permission to the ios/Runner/info.plist file:

<plist version="1.0">
		<dict>
        <key>NSLocationWhenInUseUsageDescription</key>
        <string>This app needs location access when in use</string>
        ...
    </dict>
</plist>

Ensure that the key between the <key> tags remains exactly the same. However, feel free to modify the description between the <string> tags. This description will be displayed to the user when requesting location access.

Unfortunately, I do not have access to an iOS device for testing, so I recommend checking the package's documentation if you run into issues on iOS devices.

Getting the device's location

With the package installed, we can go straight to the implementation, see the code below:

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: LocationRetriever(),
    );
  }
}

class LocationRetriever extends StatefulWidget {
  @override
  _LocationRetrieverState createState() => _LocationRetrieverState();
}

class _LocationRetrieverState extends State<LocationRetriever> {
  LocationData? _currentLocation;
  Location _location = Location();

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

  Future<void> _getLocation() async {
    await _location.serviceEnabled().then((bool enabled) async {
      if (!enabled) {
        await _location.requestService().then((bool enabled) {
          if (!enabled) return;
        });
      }
    });

    await _location.hasPermission().then((PermissionStatus status) async {
      if (status == PermissionStatus.denied) {
        await _location.requestPermission().then((PermissionStatus status) {
          if (status != PermissionStatus.granted) return;
        });
      }
    });

    try {
      await _location.getLocation().then((LocationData locationData) =>
          setState(() => _currentLocation = locationData));
    } catch (error) {
      print('Error getting location: $error');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Location Demo'),
      ),
      body: Center(
        child: _currentLocation != null
            ? Text(
                'Accuracy: ${_currentLocation!.accuracy}\n'
                'Altitude: ${_currentLocation!.altitude}\n'
                'longitude: ${_currentLocation!.longitude}\n'
                'provider: ${_currentLocation!.provider}\n'
                'speed: ${_currentLocation!.speed}\n'
                'speedAccuracy: ${_currentLocation!.speedAccuracy}\n'
                'time: ${DateTime.fromMillisecondsSinceEpoch(
                    _currentLocation!.time!.toInt()
                )}\n'
                'verticalAccuracy: ${_currentLocation!.verticalAccuracy}',
                style: TextStyle(fontSize: 20))
            : Text(
                'Getting location...',
                style: TextStyle(fontSize: 20),
              ),
      ),
    );
  }
}

In this code snippet, we created two widgets, the MyApp widget which is the root widget of the application, and the LocationRetriever widget. Which is responsible for showing the current location.

The LocationRetriever widget is a stateful widget. Inside its state, we defined two private variables _currentLocation and _location. The _currentLocation  variable is of type LocationData? and will be used to save the data of the retrieved location. The _location variable is of type Location and will be set to an instance of the Location() class coming from the location package. To make this work, we imported the package using import 'package:location/location.dart';.

The initState function, which we override, calls our _getLocation function. This function performs three essential tasks:

  1. Checks if location services are enabled and request them if needed using the package's  serviceEnabled and requestService functions.
  2. Checks for location permission and requests them if necessary using the package's hasPermission and requestPermission functions.
  3. Retrieves the current location and assigns it to the _currentLocation variable using the package's getLocation function.

Finally, in the build function, the application's user interface is constructed. If _currentLocation is null, a Text widget displays "Getting location..." indicating that the data is still loading. If _currentLocation holds a value, then we display another Text widget with location data.

flutter_retrieving_location_data_using_location_package

Continuous listening

If you are creating an application that should always be aware of the user's location, such as a run tracker, you can use the location data in a way that it is constantly updated. This way, the application consistently keeps an eye on where the user is and updates the location. With the location package, this can be achieved in the following way:

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

  _location.onLocationChanged.listen((LocationData currentLocation) =>
      setState(() => _currentLocation = currentLocation));

  _getLocation();
}

In this code snippet, we updated the initState function to also call the onLocationChanged.listen function which takes a callback. In this callback, we update the _currentLocation variable with the newly retrieved location.

As you can see in the GIF below the values keep changing, meaning the location is continuously retrieved.

flutter_continuously_listening_for_location_data_using_location_package

Receiving location in the background

When the application is in the background some applications still require location data, here is how you can do it using the location package:

@override
void initState() {
  _location.enableBackgroundMode(enable: true);
  
  super.initState();
  _getLocation();
}

In this code snippet, we changed the initState function to also call the enableBackgroundMode function on the _location variable. By setting the enable parameter to true we ensure that the location is also tracked when the application is in the background.

In the GIF below you can see that I modified the code to print out the location. As you can see when the application is closed it keeps on printing the location.

flutter_retrieving_location_data_in_the_background_using_location_package

Conclusion

By using the location package, you can easily add location features to your Flutter application. Whether you want to check the location just once or keep it updated all the time, this package makes it simple. Also, it assists in managing location permissions and even provides functions to ask for them when needed.

Tags