7.1: Location services

Contents:

Using location in an app can greatly improve the user experience by providing contextual information. For example, you may want your app to be aware that the user is at the gym to start tracking their fitness, or to turn on Wi-Fi when they go to a friend's house.

The system obtains the device's location using a combination of GPS, Wi-Fi, and cell network technologies. Google Play services provides the FusedLocationProviderClient class, a convenient way to let the system make location requests on your behalf. A fused location service estimates device location by combining, or "fusing," all the available location providers, including GPS location and network location. It does this to balance fast, accurate results with minimal battery drain.

The fused location provider methods return a Location object, which contains geographic coordinates in the form of latitude and longitude. If your app requires a physical address, you can convert from latitude/longitude coordinates (and back) using the Geocoder class.

The user controls location settings on their device, including settings for location precision. The user can also turn off location services completely. If your app requires location services, you can detect the current location settings and prompt the user to change them in a system dialog.

Setting up Google Play services

Before you can use the features provided by Google Play services, you must install the Google Repository, which includes the Google Play services SDK. To install the Google Repository and update the Android SDK Manager:

  1. Open Android Studio.
  2. Select Tools > Android > SDK Manager.
  3. Select the SDK Tools tab.
  4. Expand Support Repository, select Google Repository, and click OK.

To add Google Play services to your project, add the following line of code to the dependencies section in your app-level build.gradle (Module: app) file:

compile 'com.google.android.gms:play-services:XX.X.X'

Replace XX.X.X with the latest version number, for example 11.0.2. Android Studio will let you know if you need to update it. For more information and the latest version number, see Add Google Play Services to Your Project.

Note: If your app references more than 65K methods, the app may fail to compile. To mitigate this problem, compile your app using only the Google Play services APIs that your app uses. To learn how to selectively compile APIs into your executable, see Set Up Google Play Services in the developer documentation. To learn how to enable an app configuration known as multidex , see Configure Apps with Over 64K Methods.

Now that you have Google Play services installed, you're ready to connect to the LocationServices API.

Requesting location permissions

Apps that use location services must request location permissions. Android offers two location permissions that you can include in the manifest:

Starting with Android 6.0 (API level 23), you also have to request "dangerous" permissions at runtime. The user can revoke permissions at any time, so each time you use location services, check for permissions. For more information, see Requesting Permissions at Runtime.

Requesting the last known location

Once your app connects to the LocationServices API, here's how to get the last known location of a user's device:

  1. Create an instance of a FusedLocationProviderClient using the LocationServices.getFusedLocationProviderClient() method.
  2. Call the FusedLocationProviderClient getLastLocation() method to retrieve the device location. The precision of the location returned by this call is determined by the permission setting you put in your app manifest, as described above.

The getLastLocation() method returns a Task object. A Task object represents an asynchronous operation (usually an operation to fetch some value, in this case a Location object). You retrieve the latitude and longitude of a location from the Location object. In rare cases when the location is not available, the Location object is null.

The Task class supplies methods for adding success and failure listeners. If the operation is successful, the success listener delivers the desired object. If the operation is unsuccessful, the failure listener delivers an Exception:

mFusedLocationClient.getLastLocation().addOnSuccessListener(
       new OnSuccessListener<Location>() {
           @Override
           public void onSuccess(Location location) {
               if (location != null) {
                   mLastLocation = location;
               } 
           }
       });

mFusedLocationClient.getLastLocation().addOnFailureListener(
       new OnFailureListener() {
           @Override
           public void onFailure(@NonNull Exception e) {
               Log.e(TAG, "onFailure: ", e.printStackTrace());

           }
       }
);

Note that the getLastLocation() method doesn't actually force the fused location provider to obtain a new location. Instead, it retrieves the most recently obtained location from a local cache. On a physical device, there is usually a service that fetches and caches the location right after the device is restarted.

An emulator, however, does not have such a service, so getLastLocation() is more likely to return null. To force the fused location provider to obtain a new location, start the Google Maps app and accept the terms and conditions (if you haven't already). Use the Location tab of the emulator settings to deliver a location to the fused location provider. This forces a value to be cached, and getLastLocation() no longer returns null.

Geocoding and reverse geocoding

  • Geocoding is the process of converting a street address into a set of coordinates (like latitude and longitude).
  • Reverse geocoding is the process of converting a set of coordinates into a human-readable address.

The location services API methods provide information about the device's location using Location objects. A Location object contains a latitude, longitude, timestamp, and optional parameters such as bearing, speed, and altitude. Although latitude and longitude are useful for calculating distance or displaying a map position, in many cases the address of the location is more useful. For example, if you want to let your users know where they are or what is close by, a street address is more meaningful than the latitude and longitude.

You can use the Geocoder class to do both geocoding and reverse geocoding. The amount of detail in a reverse-geocoded location description varies; for example it might contain the full street address of the closest building, or only a city name and postal code.

The Geocoder class supplies the following methods:

  • getFromLocation(double latitude, double longitude, int maxResults): Use this method for reverse geocoding. The getFromLocation() method takes coordinates as arguments and returns a list of Address objects.
    List<Address> addresses = geocoder.getFromLocation(
         location.getLatitude(), location.getLongitude(), 1);
    
  • getFromLocationName(String locationName, int maxResults): Use this method to geocode a location name into a set of coordinates. Like the getFromLocation() method, the getFromLocationName() method returns a list of Address objects that contain latitude and longitude coordinates. It can also be used with additional parameter to specify a rectangle to search within.
    List<Address> addresses = geocoder
    .getFromLocationName("731 Market St, San Francisco, CA 94103", 1)
    Address firstAddress = addresses.get(0);
    double latitude = firstAddress.getLatitude();
    double longitude = firstAddress.getLongitude();
    
    Both getFromLocation() and getFromLocationName() perform a network lookup and therefore may take a while to complete. For this reason you should not call them on the main thread.
Note: The Geocoder class requires a backend service that is not included in the core Android framework. The Geocoder query methods return an empty list if there is no backend service in the platform. To determine whether a Geocoder implementation exists, use the isPresent() method.

Creating a LocationRequest object

If your app requires precise tracking that involves periodic updates, you need to create a LocationRequest object that contains the requirements of the location updates. The fused location provider attempts to batch location requests from different apps together, in order to preserve battery.

The options you can set for a LocationRequest include:

  • setInterval(): This method sets how frequently your app needs location updates, in milliseconds. If another app is receiving updates more frequently, location updates may be more frequent than this interval. Updates may also be less frequent than this interval, or there may be no updates at all, for example if the device has no connectivity.
  • setFastestInterval(): This method sets a limit, in milliseconds, to the update rate. The location APIs send out updates at the fastest rate that any app has requested with setInterval(). If this rate is faster than your app can handle, you may encounter UI flicker or data overflow. To prevent these problems, call setFastestInterval().
  • setPriority(): This method sets the priority of the request, which gives the location APIs a strong hint about which location sources to use. The following values are supported:
  • PRIORITY_BALANCED_POWER_ACCURACY: Use this setting to request location precision to within a city block, which is an accuracy of approximately 100 meters. This is considered a coarse level of accuracy, and is likely to consume less power. With this setting, the location services are likely to use Wi-Fi and cell-tower positioning to determine location. Note, however, that the choice of location provider depends on many other factors, such as which sources are available.
  • PRIORITY_HIGH_ACCURACY: Use this setting to request the most precise location possible. With this setting, the location services are more likely to use GPS to determine location.
  • PRIORITY_LOW_POWER: Use this setting to request city-level precision, which is an accuracy of approximately 10 kilometers. This is considered a coarse level of accuracy, and is likely to consume less power.
  • PRIORITY_NO_POWER: Use this setting if you need zero additional power consumption, but want to receive location updates when available. With this setting, your app does not trigger any location updates, but receives locations triggered by other apps.

For a list of other options you can set, see LocationRequest.

private LocationRequest getLocationRequest() {
   LocationRequest locationRequest = new LocationRequest();
   locationRequest.setInterval(10000);
   locationRequest.setFastestInterval(5000);
   locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
   return locationRequest;
}

Working with the user's location settings

Activating the GPS and network capabilities of a device uses significant power. Additionally, users may have privacy concerns about their location being tracked.This is why users have a device setting that lets them control the balance between accuracy and power for location requests. Users can also turn off location services completely.

The user's settings may reduce the accuracy of location tracking to the point where it reduces your app's usability. For example, if the user turns off location, it doesn't make sense to have a tracker app running and using system resources. You can detect the device settings and prompt the user to change them to match what you've set in your LocationRequest.  The dialog shown to the user to update their location settings

To check the device settings:

  1. Create a LocationSettingsRequest and add one or more location requests.
     LocationSettingsRequest settingsRequest = new LocationSettingsRequest.Builder()
          .addLocationRequest(mLocationRequest).build();
    
  2. Create a SettingsClient object using the LocationServices.getSettingsClient() method, and pass in the context:
     SettingsClient client = LocationServices.getSettingsClient(this);
    
  3. Use the SettingsClient checkLocationSettings() method to see if the device settings match the LocationRequest. Pass in the LocationSettingsRequest that you created in step 1:

     Task<LocationSettingsResponse> task = client
            .checkLocationSettings(settingsRequest);
    

    The checkLocationSettings() method returns a Task object, just like the getLastLocation() method discussed above.

  4. Use the OnFailureListener to catch the case where the device settings do not match the LocationRequest.

    The Exception passed into the resulting onFailure() method contains a Status object with information about whether the device settings match the LocationRequest.

    If the device settings don't match the LocationRequest, the status code is LocationSettingsStatusCodes.RESOLUTION_REQUIRED. In this case, show the user a dialog that prompts them to change their settings, then handle the user's decision to either change their settings or back out:

  5. Call the startResolutionForResult() method on the status.

     task.addOnFailureListener(this, new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception e) {
            int statusCode = ((ApiException) e).getStatusCode();
            if (statusCode
                    == LocationSettingsStatusCodes
                    .RESOLUTION_REQUIRED) {
                // Location settings are not satisfied, but this can
                // be fixed by showing the user a dialog
                try {
                    // Show the dialog by calling
                    // startResolutionForResult(), and check the
                    // result in onActivityResult()
                    ResolvableApiException resolvable =
                            (ResolvableApiException) e;
                    resolvable.startResolutionForResult
                            (MainActivity.this,
                                    REQUEST_CHECK_SETTINGS);
                } catch (IntentSender.SendIntentException sendEx) {
                    // Ignore the error
                }
            }
        }
     });
    
  6. Override the onActivityResult() callback in your Activity to handle the user's decision (either to update their settings or to back out). Make sure the requestCode matches the constant that you used in the startResolutionForResult() method.

     @Override
     protected void onActivityResult(int requestCode, int resultCode,
            Intent data) {
        if (requestCode == REQUEST_CHECK_SETTINGS) {
            if (resultCode == RESULT_CANCELED) {
                stopTrackingLocation();
            } else if (resultCode == RESULT_OK) {
                startTrackingLocation();
            }
        }
    
        super.onActivityResult(requestCode, resultCode, data);
     }
    

Requesting location updates

After checking the device settings and prompting the user to update them if they don't match your location request, you can start requesting periodic location updates using the LocationRequest and the FusedLocationProviderClient objects. The accuracy of the location is determined by the available location providers (network and GPS), the location permissions you requested, and the options you set in the location request.

To request and start location updates:

  1. Create a LocationRequest object with the desired parameters for your location updates, as described in Creating a LocationRequest object above.
  2. The fused location provider invokes the LocationCallback.onLocationResult() callback method. Create an instance of LocationCallback, and override its onLocationResult() method:
     mLocationCallback = new LocationCallback() {
             @Override
             public void onLocationResult(LocationResult locationResult) {
                 for (Location location : locationResult.getLocations()) {
                     // Update UI with location data
                     // ...
                 }
             };
     }
    
  3. To start the regular updates, call requestLocationUpdates() on the FusedLocationProviderClient. Pass in the LocationRequest and LocationCallback. This starts the location updates, which are delivered to the onLocationResult() method.

Stopping location updates

When you no longer need location updates, stop them using the FusedLocationProviderClient removeLocationUpdates() method, passing it your instance of the LocationCallback.

You might want to stop location updates when the activity is no longer in focus, for example when the user switches to another app or to a different activity in the same app. Stopping location updates at these times can reduce power consumption, provided the app doesn't need to collect information even when it's running in the background.

The related practical documentation is in 7.1 P: Using the device location.

Learn more

Android developer documentation:

results matching ""

    No results matching ""