Receive periodic location updates

If your app can continuously track location, it can deliver more relevant information to the user. For example, if your app helps the user find their way while walking or driving, or if your app tracks the location of assets, it needs to get the location of the device at regular intervals. As well as the geographical location (latitude and longitude), you may want to give the user further information such as the bearing (horizontal direction of travel), altitude, or velocity of the device. This information, and more, is available in the Location object that your app can retrieve from the fused location provider.

While you can get a device's location with getLastLocation(), as illustrated in the lesson on Getting the Last Known Location, a more direct approach is to request periodic updates from the fused location provider. In response, the API updates your app periodically with the best available location, based on the currently-available location providers such as WiFi and GPS (Global Positioning System). The accuracy of the location is determined by the providers, the location permissions you've requested, and the options you set in the location request.

This lesson shows you how to request regular updates about a device's location using the requestLocationUpdates() method in the fused location provider.

Declare permissions

Apps that use location services must request location permissions. In most cases, you can request the coarse location permission and still get reasonably accurate location information from the available location providers.

The following snippet demonstrates how to request the coarse location permission:

<manifest ... >
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
</manifest>
Screen capture of user-facing dialog
Figure 1. Dialog shown when app requests location with option to only access location while the app is in use

On a device that runs Android 10 (API level 29) or higher, users see the dialog shown in Figure 1 to indicate that your app is requesting a location permission. If the user allows your app access to the device's location from this dialog, your app is able to access location only while the user is interacting with the app, not while running in the background. You can declare a foreground service, allowing your app to get location details to continue a user-initiated action after the user has sent your app to the background.

Note: Although it's possible to request access to background location if your app runs on Android 10 or higher, it's highly recommended that you not do so.

Get the last known location

The last known location of the device provides a handy base from which to start, ensuring that the app has a known location before starting the periodic location updates. The lesson on Getting the Last Known Location shows you how to get the last known location by calling getLastLocation(). The snippets in the following sections assume that your app has already retrieved the last known location and stored it as a Location object in the global variable mCurrentLocation.

Request location updates

Before requesting location updates, your app must connect to location services and make a location request. The lesson on Changing Location Settings shows you how to do this. Once a location request is in place you can start the regular updates by calling requestLocationUpdates().

Depending on the form of the request, the fused location provider either invokes the LocationCallback.onLocationResult() callback method and passes it a list of Location objects, or issues a PendingIntent that contains the location in its extended data. The accuracy and frequency of the updates are affected by the location permissions you've requested and the options you set in the location request object.

This lesson shows you how to get the update using the LocationCallback callback approach. Call requestLocationUpdates(), passing it your instance of the LocationRequest object, and a LocationCallback. Define a startLocationUpdates() method as shown in the following code sample:

Kotlin

override fun onResume() {
    super.onResume()
    if (requestingLocationUpdates) startLocationUpdates()
}

private fun startLocationUpdates() {
    fusedLocationClient.requestLocationUpdates(locationRequest,
            locationCallback,
            Looper.getMainLooper())
}

Java

@Override
protected void onResume() {
    super.onResume();
    if (requestingLocationUpdates) {
        startLocationUpdates();
    }
}

private void startLocationUpdates() {
    fusedLocationClient.requestLocationUpdates(locationRequest,
            locationCallback,
            Looper.getMainLooper());
}

Notice that the above code snippet refers to a boolean flag, requestingLocationUpdates, used to track whether the user has turned location updates on or off. If users have turned location updates off, you can inform them of your app's location requirement. For more about retaining the value of the boolean flag across instances of the activity, see Save the State of the Activity.

Define the location update callback

The fused location provider invokes the LocationCallback.onLocationResult() callback method. The incoming argument contains a list Location object containing the location's latitude and longitude. The following snippet shows how to implement the LocationCallback interface and define the method, then get the timestamp of the location update and display the latitude, longitude and timestamp on your app's user interface:

Kotlin

private lateinit var locationCallback: LocationCallback

// ...

override fun onCreate(savedInstanceState: Bundle?) {
    // ...

    locationCallback = object : LocationCallback() {
        override fun onLocationResult(locationResult: LocationResult?) {
            locationResult ?: return
            for (location in locationResult.locations){
                // Update UI with location data
                // ...
            }
        }
    }
}

Java

private LocationCallback locationCallback;

// ...

@Override
protected void onCreate(Bundle savedInstanceState) {
    // ...

    locationCallback = new LocationCallback() {
        @Override
        public void onLocationResult(LocationResult locationResult) {
            if (locationResult == null) {
                return;
            }
            for (Location location : locationResult.getLocations()) {
                // Update UI with location data
                // ...
            }
        };
    };
}

Request access to background location

If your app targets Android 10 or higher, you must declare the ACCESS_BACKGROUND_LOCATION permission in your app's manifest file and receive user permission in order to receive regular location updates while your app is in the background.

The following code snippet shows how to request background location access in your app:

<manifest ... >
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
</manifest>
Screen capture of user-facing dialog
Figure 2. Dialog shown when app requests location with option to access location all the time, including while in the background

On a device that runs Android 10 (API level 29) or higher, users see the dialog shown in Figure 2 to indicate that your app is requesting a location permission and additionally requests access to location all the time, including while in the background. This dialog includes an option for users to give your app access to location only while using the app; if users choose this option, your app doesn't have access to location while in the background. If your app's workflow requires all-the-time access to location information, you should inform the user of your app's background location requirement.

Caution: Even if users initially give your app access to location information while in the background, users can later revoke this permission in system settings. Users can choose to give your app access to location information only while they're using your app, or they can choose to not give your app access to location at all.

For this reason, whenever your app starts a service, check whether the user still allows your app to access location information in the background.

User-facing reminder of background location access

Screen capture of system notification
Figure 3. Notification reminding user that they've granted an app "all-the-time" access to device location

The user can choose to allow your app all-the-time access to device location. When your app accesses device location in the background for the first time after the user makes this choice, the system schedules a notification to send to the user. This notification reminds the user that they've allowed your app to access device location all the time. An example notification appears in Figure 3.

Inform user of background location requirement

If the user has requested that your app access location only while they're using your app, display a custom dialog to alert the user that a workflow within your app cannot function properly without access to their location all the time.

Caution: Users have the option to simultaneously deny access to the device's location and prevent your app from requesting access to the device's location in the future. Your app should respect and handle this "deny and don't ask again" decision.

After the user acknowledges this dialog, you can request background location, at which time the system dialog shown in Figure 4 appears:

Screen capture of user-facing dialog
Figure 4. Dialog requesting user consent for all-the-time access to location

An example of this permission-checking logic appears in the following code snippet:

Kotlin

val permissionAccessCoarseLocationApproved = ActivityCompat
    .checkSelfPermission(this, permission.ACCESS_COARSE_LOCATION) ==
    PackageManager.PERMISSION_GRANTED

if (permissionAccessCoarseLocationApproved) {
   val backgroundLocationPermissionApproved = ActivityCompat
       .checkSelfPermission(this, permission.ACCESS_BACKGROUND_LOCATION) ==
       PackageManager.PERMISSION_GRANTED

   if (backgroundLocationPermissionApproved) {
       // App can access location both in the foreground and in the background.
       // Start your service that doesn't have a foreground service type
       // defined.
   } else {
       // App can only access location in the foreground. Display a dialog
       // warning the user that your app must have all-the-time access to
       // location in order to function properly. Then, request background
       // location.
       ActivityCompat.requestPermissions(this,
           arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
           your-permission-request-code
       )
   }
} else {
   // App doesn't have access to the device's location at all. Make full request
   // for permission.
   ActivityCompat.requestPermissions(this,
       arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,
               Manifest.permission.ACCESS_BACKGROUND_LOCATION),
       your-permission-request-code
   )
}

Java

boolean permissionAccessCoarseLocationApproved =
    ActivityCompat.checkSelfPermission(this, permission.ACCESS_COARSE_LOCATION)
        == PackageManager.PERMISSION_GRANTED;

if (permissionAccessCoarseLocationApproved) {
   boolean backgroundLocationPermissionApproved =
           ActivityCompat.checkSelfPermission(this,
               permission.ACCESS_BACKGROUND_LOCATION)
               == PackageManager.PERMISSION_GRANTED;

   if (backgroundLocationPermissionApproved) {
       // App can access location both in the foreground and in the background.
       // Start your service that doesn't have a foreground service type
       // defined.
   } else {
       // App can only access location in the foreground. Display a dialog
       // warning the user that your app must have all-the-time access to
       // location in order to function properly. Then, request background
       // location.
       ActivityCompat.requestPermissions(this, new String[] {
           Manifest.permission.ACCESS_BACKGROUND_LOCATION},
           your-permission-request-code);
   }
} else {
   // App doesn't have access to the device's location at all. Make full request
   // for permission.
   ActivityCompat.requestPermissions(this, new String[] {
        Manifest.permission.ACCESS_COARSE_LOCATION,
        Manifest.permission.ACCESS_BACKGROUND_LOCATION
        },
        your-permission-request-code);
}

Continue a user-initiated action

Your app might offer location-dependent workflows, such as using turn-by-turn navigation while driving or tracing a path while running. As users perform these types of tasks, your app usually needs to access the device's location after being placed in the background, such as when the user presses the Home button on their device or turns their device's display off.

To retain access to the device's location in this specific type of use case, start a foreground service that you've declared as having a foreground service type of "location" in your app's manifest:

<service
    android:name="MyNavigationService"
    android:foregroundServiceType="location" ... >
    ...
</service>

Before starting the foreground service, make sure that your app still has access to the device's location:

Kotlin

val permissionAccessCoarseLocationApproved = ActivityCompat
    .checkSelfPermission(this, permission.ACCESS_COARSE_LOCATION) ==
    PackageManager.PERMISSION_GRANTED

if (permissionAccessCoarseLocationApproved) {
   // App has permission to access location in the foreground. Start your
   // foreground service that has a foreground service type of "location".
} else {
   // Make a request for foreground-only location access.
   ActivityCompat.requestPermissions(this,
       arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION),
       your-permission-request-code
   )
}

Java

boolean permissionAccessCoarseLocationApproved =
    ActivityCompat.checkSelfPermission(this,
        permission.ACCESS_COARSE_LOCATION) ==
        PackageManager.PERMISSION_GRANTED;

if (permissionAccessCoarseLocationApproved) {
    // App has permission to access location in the foreground. Start your
    // foreground service that has a foreground service type of "location".
} else {
   // Make a request for foreground-only location access.
   ActivityCompat.requestPermissions(this, new String[] {
        Manifest.permission.ACCESS_COARSE_LOCATION},
       your-permission-request-code);
}

Stop location updates

Consider whether you want to stop the location updates when the activity is no longer in focus, such as when the user switches to another app or to a different activity in the same app. This can be handy to reduce power consumption, provided the app doesn't need to collect information even when it's running in the background. This section shows how you can stop the updates in the activity's onPause() method.

To stop location updates, call removeLocationUpdates(), passing it a LocationCallback, as shown in the following code sample:

Kotlin

override fun onPause() {
    super.onPause()
    stopLocationUpdates()
}

private fun stopLocationUpdates() {
    fusedLocationClient.removeLocationUpdates(locationCallback)
}

Java

@Override
protected void onPause() {
    super.onPause();
    stopLocationUpdates();
}

private void stopLocationUpdates() {
    fusedLocationClient.removeLocationUpdates(locationCallback);
}

Use a boolean, mRequestingLocationUpdates, to track whether location updates are currently turned on. In the activity's onResume() method, check whether location updates are currently active, and activate them if not:

Kotlin

override fun onResume() {
    super.onResume()
    if (requestingLocationUpdates) startLocationUpdates()
}

Java

@Override
protected void onResume() {
    super.onResume();
    if (requestingLocationUpdates) {
        startLocationUpdates();
    }
}

Save the state of the activity

A change to the device's configuration, such as a change in screen orientation or language, can cause the current activity to be destroyed. Your app must therefore store any information it needs to recreate the activity. One way to do this is via an instance state stored in a Bundle object.

The following code sample shows how to use the activity's onSaveInstanceState() callback to save the instance state:

Kotlin

override fun onSaveInstanceState(outState: Bundle?) {
    outState?.putBoolean(REQUESTING_LOCATION_UPDATES_KEY, requestingLocationUpdates)
    super.onSaveInstanceState(outState)
}

Java

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putBoolean(REQUESTING_LOCATION_UPDATES_KEY,
            requestingLocationUpdates);
    // ...
    super.onSaveInstanceState(outState);
}

Define an updateValuesFromBundle() method to restore the saved values from the previous instance of the activity, if they're available. Call the method from the activity's onCreate() method, as shown in the following code sample:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    // ...
    updateValuesFromBundle(savedInstanceState)
}

private fun updateValuesFromBundle(savedInstanceState: Bundle?) {
    savedInstanceState ?: return

    // Update the value of requestingLocationUpdates from the Bundle.
    if (savedInstanceState.keySet().contains(REQUESTING_LOCATION_UPDATES_KEY)) {
        requestingLocationUpdates = savedInstanceState.getBoolean(
                REQUESTING_LOCATION_UPDATES_KEY)
    }

    // ...

    // Update UI to match restored state
    updateUI()
}

Java

@Override
public void onCreate(Bundle savedInstanceState) {
    // ...
    updateValuesFromBundle(savedInstanceState);
}

private void updateValuesFromBundle(Bundle savedInstanceState) {
    if (savedInstanceState == null) {
        return;
    }

    // Update the value of requestingLocationUpdates from the Bundle.
    if (savedInstanceState.keySet().contains(REQUESTING_LOCATION_UPDATES_KEY)) {
        requestingLocationUpdates = savedInstanceState.getBoolean(
                REQUESTING_LOCATION_UPDATES_KEY);
    }

    // ...

    // Update UI to match restored state
    updateUI();
}

For more about saving instance state, see the Android Activity class reference.

Note: For a more persistent storage, you can store the user's preferences in your app's SharedPreferences. Set the shared preference in your activity's onPause() method, and retrieve the preference in onResume(). For more information about saving preferences, read Saving Key-Value Sets.

The next lesson, Display a location address, shows you how to display the street address for a given location.

Additional resources

To learn more, take advantage of the following resources:

Samples