Flutter - GPS Geolocation Tutorial

GPS has become a standard feature on modern smartphones. It's usually used by applications to get the device location. This tutorial shows you how to access device location in Flutter using GPS, including how to get permissions, get current location and continuous location update.

Dependencies

A Flutter package geolocator provides geolocation functionalities. Add it in your pubspec.yaml file and run Get Packages.

  dependencies {
    ...
    geolocator: ^3.0.1
    ...
  }

Permissions

You need to add permissions to each platform. For Android, you need ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION. Add the following in AndroidManifest.xml.

  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

For iOS, you need NSLocationWhenInUseUsageDescription permission. Add it in the Info.plist file.

  <key>NSLocationWhenInUseUsageDescription</key>
  <string>This app needs access to location when open.</string>

Code Example

Below is the code structure for this tutorial. We need to create an instance of Geolocator and store the value of latest Position. The application will use the Position value to display the latitude and the longitude.

  import 'dart:async';
  import 'package:flutter/material.dart';
  import 'package:geolocator/geolocator.dart';

  class GeolocationExampleState extends State<GeolocationExample> {
    Geolocator _geolocator;
    Position _position;

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

      _geolocator = Geolocator();
    }

    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text('Flutter Geolocation Example'),
        ),
        body: Center(
            child: Text(
               'Latitude: ${_position != null ? _position.latitude.toString() : '0'},'
                  ' Longitude: ${_position != null ? _position.longitude.toString() : '0'}'
            )
        ),
      );
    }
  }

Check Permission

If you've added the right permissions, the application will be granted with permissions to access the device location using GPS. In Android 6.0 and above, it will ask the user to grant the permission. But if you need to check whether the application has permission to access location, you can do it programatically. To do so, use checkGeolocationPermissionStatus method which returns Future<GeolocationState>. Optionally, for iOS, you can check locationAlways and locationWhenInUse separately by passing locationPermission optional parameter whose type is GeolocationPermission

  void checkPermission() {
    _geolocator.checkGeolocationPermissionStatus().then((status) { print('status: $status'); });
    _geolocator.checkGeolocationPermissionStatus(locationPermission: GeolocationPermission.locationAlways).then((status) { print('always status: $status'); });
    _geolocator.checkGeolocationPermissionStatus(locationPermission: GeolocationPermission.locationWhenInUse)..then((status) { print('whenInUse status: $status'); });
  }

Get Current Location

Getting the current location is very simple. Just use getCurrentPosition method which returns Future<Location>. You can pass desiredAccuracy option.

  await Geolocator().getCurrentPosition(desiredAccuracy: LocationAccuracy.high)

Sometimes the process of getting current location may fail, for example if the user turns off the GPS sensor. If it has been turned of since the beginning, it may cause error, so we need to catch the error. There's also possible the GPS is turned off while the process of finding location is on going. On this case, it may cause the process stuck, and therefore it's better to add execution timeout.

  void updateLocation() async {
    try {
      Position newPosition = await Geolocator().getCurrentPosition(desiredAccuracy: LocationAccuracy.high)
          .timeout(new Duration(seconds: 5));

      setState(() {
        _position = newPosition;
      });
    } catch (e) {
      print('Error: ${e.toString()}');
    }
  }

Below are the descriptions of each LocationAccuracy value.

NameDescription
lowestLocation is accurate within a distance of 3000m on iOS and 500m on Android.
lowLocation is accurate within a distance of 1000m on iOS and 500m on Android.
mediumLocation is accurate within a distance of 10m on iOS and between 0m and 100m on Android.
highLocation is accurate within a distance of 10m on iOS and between 0m and 100m on Android.
bestLocation is accurate within a distance of ~0m on iOS and between 0m and 100m on Android.
bestForNavigationLocation is accuracy is optimized for navigation on iOS and matches LocationAccuracy.best on Android.

Location Update Stream

To get the updated location, actually you can put the code above in a while loop. But, there's a better and cleaner way. You can use getPositionStream which returns Stream<Subscription>. You can also set how much location change is needed before the listener is notified using distanceFilter.

  LocationOptions locationOptions = LocationOptions(accuracy: LocationAccuracy.high, distanceFilter: 1);

  StreamSubscription positionStream = _geolocator.getPositionStream(locationOptions).listen(
            (Position position) {
          _position = position;
        });

Below is the full code of this tutorial

  import 'dart:async';
  import 'package:flutter/material.dart';
  import 'package:geolocator/geolocator.dart';
  
  void main() => runApp(MyApp());
  
  class MyApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: 'Flutter Geolocation',
        home: GeolocationExample(),
      );
    }
  }
  
  class GeolocationExampleState extends State {
    Geolocator _geolocator;
    Position _position;
  
    void checkPermission() {
      _geolocator.checkGeolocationPermissionStatus().then((status) { print('status: $status'); });
      _geolocator.checkGeolocationPermissionStatus(locationPermission: GeolocationPermission.locationAlways).then((status) { print('always status: $status'); });
      _geolocator.checkGeolocationPermissionStatus(locationPermission: GeolocationPermission.locationWhenInUse)..then((status) { print('whenInUse status: $status'); });
    }
  
    @override
    void initState() {
      super.initState();
  
      _geolocator = Geolocator();
      LocationOptions locationOptions = LocationOptions(accuracy: LocationAccuracy.high, distanceFilter: 1);
  
      checkPermission();
  //    updateLocation();
  
      StreamSubscription positionStream = _geolocator.getPositionStream(locationOptions).listen(
              (Position position) {
            _position = position;
          });
    }
  
    void updateLocation() async {
      try {
        Position newPosition = await Geolocator().getCurrentPosition(desiredAccuracy: LocationAccuracy.high)
            .timeout(new Duration(seconds: 5));
  
        setState(() {
          _position = newPosition;
        });
      } catch (e) {
        print('Error: ${e.toString()}');
      }
    }
  
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text('Startup Name Generator'),
        ),
        body: Center(
            child: Text(
                'Latitude: ${_position != null ? _position.latitude.toString() : '0'},'
                    ' Longitude: ${_position != null ? _position.longitude.toString() : '0'}'
            )
        ),
      );
    }
  }
  
  class GeolocationExample extends StatefulWidget {
    @override
    GeolocationExampleState createState() => new GeolocationExampleState();
  }

.