Android 10 and 11 gives users more control over apps' access to device location.

When an app running on Android 11 requests location access, users have four options:

Android 10

Android 11

In this codelab, you'll learn how to receive location updates and how to support location on any version of Android, particularly Android 10 and Android 11. At the end of the codelab, you can expect to have an app that follows the current best practices for retrieving location updates.

If you run into any issues (code bugs, grammatical errors, unclear wording, etc.) as you work through this codelab, please report the issue via the Report a mistake link in the lower left corner of the codelab.

Prerequisites

Familiarity with Android development, some familiarity with Activities and Services, and some familiarity with Android permissions.

What you'll learn

What you'll build

You will modify a pre-existing app to add support for requesting location access by adding code for subscribing/unsubscribing to location. You'll then add support for Android 10 and 11 by adding logic to access location in the foreground location, or while-in-use.

What you'll need

Clone the starter project repo

To get you started as quickly as possible, we have prepared a starter project for you to build on. If you have git installed, you can simply run the command below. (You can check by typing git --version in the terminal / command line and verify it executes correctly.)

 git clone https://github.com/googlecodelabs/while-in-use-location

Feel free to visit the Github page directly: https://github.com/googlecodelabs/while-in-use-location

If you do not have git, you can get the project as a zip file:

Download Zip

Import the project

Start Android Studio, select "Open an existing Android Studio project" from the Welcome screen, and open the project directory.

After the project has loaded, you may also see an alert that Git isn't tracking all your local changes. You can click "Ignore" or the "X" in the upper right. (You won't be pushing any changes back to the Git repo.)

In the upper-left corner of the project window, you should see something like the image below if you are in the Android view. (If you are in the Project view, you will need to expand the project to see the same thing.)

There are two folder icons (base and complete). Each of them are known as a "module".

Please note that Android Studio might take several seconds to compile the project in the background for the first time. During this time you will see the following message in the status bar at the bottom of Android Studio:

We recommend that you wait until Android Studio has finished indexing and building the project before making code changes. This will allow Android Studio to pull in all the necessary components.

If you get a prompt saying "Reload for language changes to take effect?" or something similar, select "Yes".

Understand the starter project

All right, you're set up and ready to request location in the app. We'll be using the base module as the starting point. During each step, you'll add code to the base module. By the time you're done with this codelab, the code in the base module should match the contents of the complete module.

The complete module can be used for checking your work, or for you to reference if you encounter any issues.

Overview of key components:

Emulator setup

If you need help setting up an Android emulator, refer to the Run your app article.

Run the starter project

Let's run our app.

  1. Connect your Android device to your computer or start an emulator. Make sure that the device is running Android 10 or higher.)
  2. In the toolbar, select the base configuration from the drop-down selector and click the Run (green triangle) button next to it:


  1. You should see the following application appear on your device:

  2. You may notice that no location information appears in the output screen. That's because we haven't added the location code yet. We'll do that now.

Concepts

The focus of this codelab is to show you how to receive location updates and eventually support Android 10 and Android 11.

However, before we get started coding, it makes sense to review the basics.

Types of Location Access

You may remember at the start of the codelab that we mentioned four different options for location access. Let's take a look at what they mean:

Services, Foreground Services, and Binding

To fully support Allow only while using the app location updates, you need to account for when the user navigates away from your app. If you wish to continue receiving updates in that situation, you need to create a foreground Service and associate it with a Notification.

In addition, if you want to use the same Service to request location updates when your app is visible and when the user navigates away from your app, you will need to bind/unbind that Service to the UI element.

Because this codelab focuses only on getting Location updates, we've written all the code for you in the ForegroundOnlyLocationService.kt class. You can browse through that class and the MainActivity.kt to see how they work together.

If you want to learn more check out our guide on Services and Bound Services.

Permissions

In order to receive location updates from a NETWORK_PROVIDER or GPS_PROVIDER, you must request the user's permission by declaring either the ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission, respectively, in your Android manifest file. Without these permissions, your application won't be able to request access to location at runtime.

Those permissions cover both the One time only and the Allow only while using the app cases when your app is used on a device running Android 10 or higher.

Location

Your app can access the set of supported location services through classes in the com.google.android.gms.location package.

Let's look at the main classes:

Now that you have a basic idea of what we are doing, let's get started with the code!

Adding Location Features

This codelab will focus on the most common location option, Allow only while using the app.

To receive location updates, your app must either have a visible activity or a service running in the foreground (with a notification).

Permissions

The purpose of this codelab is to show how to receive location updates, not how to request location permissions, so we've already written the permission-based code for you. Feel free to skip it if you already understand it.

Below are the permission highlights (no actions are required for this portion):

  1. Declare what permission(s) you use in the AndroidManifest.xml.
  2. Before attempting to access location information, check whether the user has given your app permission to do so. If your app hasn't received permission yet, request access.
  3. Handle the user's permission choice. (You can see this code in the MainActivity.kt.)

If you search for TODO: Step 1.0, Review Permissions in the AndroidManifest.xml or the MainActivity.kt, you will see all the code we wrote for permissions.

If you would like to review an in-depth permission tutorial, check out our permissions guide.

Now, let's start writing some location code.

Review the key variables needed for location updates

In the base module, search for the TODO: Step 1.1, Review variables in the ForegroundOnlyLocationService.kt file.

No actions are needed in this step. You just need to review the following code block,along with the comments, to understand the key classes/variables you will be using to receive location updates.

// TODO: Step 1.1, Review variables (no changes).
// FusedLocationProviderClient - Main class for receiving location updates.
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient

// LocationRequest - Requirements for the location updates, i.e., how often you
// should receive updates, the priority, etc.
private lateinit var locationRequest: LocationRequest

// LocationCallback - Called when FusedLocationProviderClient has a new Location.
private lateinit var locationCallback: LocationCallback

// Used only for local storage of the last known location. Usually, this would be saved to your
// database, but because this is a simplified sample without a full database, we only need the
// last location to create a Notification if the user navigates away from the app.
private var currentLocation: Location? = null

Review the FusedLocationProviderClient initialization

In the base module, search for TODO: Step 1.2, Review the FusedLocationProviderClient in the ForegroundOnlyLocationService.kt file. Your code should look something like this:

// TODO: Step 1.2, Review the FusedLocationProviderClient.
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)

As mentioned in the previous comments, this is the main class for getting location updates.

We've already initialized the variable for you, but it's important to review the code to understand how it is initialized. We will be adding some code here later to request location updates.

Initialize the LocationRequest

In the base module, search for TODO: Step 1.3, Create a LocationRequest in the ForegroundOnlyLocationService.kt file.

Add the following code below the comment. The LocationRequest initialization code adds the extra quality of service parameters you need for your request (intervals, max wait time, priority, etc.).

Read through the comments to understand how each one works.

// TODO: Step 1.3, Create a LocationRequest.
locationRequest = LocationRequest().apply {
   // Sets the desired interval for active location updates. This interval is inexact. You
   // may not receive updates at all if no location sources are available, or you may
   // receive them less frequently than requested. You may also receive updates more
   // frequently than requested if other applications are requesting location at a more
   // frequent interval.
   //
   // IMPORTANT NOTE: Apps running on Android 8.0 and higher devices (regardless of
   // targetSdkVersion) may receive updates less frequently than this interval when the app
   // is no longer in the foreground.
   interval = TimeUnit.SECONDS.toMillis(60)

   // Sets the fastest rate for active location updates. This interval is exact, and your
   // application will never receive updates more frequently than this value.
   fastestInterval = TimeUnit.SECONDS.toMillis(30)

   // Sets the maximum time when batched location updates are delivered. Updates may be
   // delivered sooner than this interval.
   maxWaitTime = TimeUnit.MINUTES.toMillis(2)

   priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}

Initialize the LocationCallback

In the base module, search for TODO: Step 1.4, Initialize the LocationCallback in the ForegroundOnlyLocationService.kt file.

Add the following code below the comment. The LocationCallback you create here is the callback that the FusedLocationProviderClient will call when a new location update is available.

In our callback, we first get the latest location using a LocationResult object. After that, we notify our Activity of the new location using a local broadcast (if it is active) or we update the Notification if this service is running as a foreground Service.

Read through the comments to understand what each part does.

// TODO: Step 1.4, Initialize the LocationCallback.
locationCallback = object : LocationCallback() {
   override fun onLocationResult(locationResult: LocationResult?) {
       super.onLocationResult(locationResult)

       if (locationResult?.lastLocation != null) {

           // Normally, you want to save a new location to a database. We are simplifying
           // things a bit and just saving it as a local variable, as we only need it again
           // if a Notification is created (when user navigates away from app).
           currentLocation = locationResult.lastLocation

           // Notify our Activity that a new location was added. Again, if this was a
           // production app, the Activity would be listening for changes to a database
           // with new locations, but we are simplifying things a bit to focus on just
           // learning the location side of things.
           val intent = Intent(ACTION_FOREGROUND_ONLY_LOCATION_BROADCAST)
           intent.putExtra(EXTRA_LOCATION, currentLocation)
           LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent)

           // Updates notification content if this service is running as a foreground
           // service.
           if (serviceRunningInForeground) {
               notificationManager.notify(
                   NOTIFICATION_ID,
                   generateNotification(currentLocation))
           }
       } else {
           Log.d(TAG, "Location information isn't available.")
       }
   }
}

Subscribe to location changes

Now that we've initialized everything, we need to let the FusedLocationProviderClient know that we want to receive updates.

In the base module, search for Step 1.5, Subscribe to location changes in the ForegroundOnlyLocationService.kt file.

Add the following code below the comment. The requestLocationUpdates() call lets the FusedLocationProviderClient know that we want to receive location updates.

You probably recognize the LocationRequest and LocationCallback that we defined earlier. Those let the FusedLocationProviderClient know the quality of service parameters for our request and what it should call when it has an update.

Finally, the Looper object specifies the thread for the callback.

// TODO: Step 1.5, Subscribe to location changes.
fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper())

You may also notice that this code is within a try/catch statement. This method requires such a block because a SecurityException occurs when your app doesn't have permission to access location information.

Unsubscribe to location changes

When the app no longer needs access to location information, it's important to unsubscribe from location updates.

In the base module, search for TODO: Step 1.6, Unsubscribe to location changes in the ForegroundOnlyLocationService.kt file.

Add the code below the comment. The removeLocationUpdates() method sets up a task to let the FusedLocationProviderClient know that we no longer want to receive location updates for our LocationCallback.

The addOnCompleteListener() gives the callback for completion and executes the Task.

// TODO: Step 1.6, Unsubscribe to location changes.
val removeTask = fusedLocationProviderClient.removeLocationUpdates(locationCallback)
removeTask.addOnCompleteListener { task ->
   if (task.isSuccessful) {
       Log.d(TAG, "Location Callback removed.")
       stopSelf()
   } else {
       Log.d(TAG, "Failed to remove Location Callback.")
   }
}

As with the previous step, you may have noticed that this code is within a try/catch statement. This method requires such a block because a SecurityException occurs when your app doesn't have permission to access location information

You may wonder when the methods that contain the subscribe/unsubscribe code are called. They are triggered in the main class when the user taps the button. If you wish to see it, have a look at the MainActivity.kt class.

Run app

Run your app from Android Studio and try the location button.

You should now see location information in the output screen. This is a fully functional app for Android 9.

In this section, you'll add support for Android 10.

Our app already subscribes to location changes, so there isn't a lot of work to do.

In fact, all you have to do is specify that your foreground service is used for location purposes.

Let's add support for this now.

Target SDK 29

In the base module, search for the TODO: Step 2.1, Target SDK 10 in the build.gradle file.

Make these changes:

  1. Set compileSdkVersion to 29
  2. Set buildToolsVersion to "29.0.3" (Include the quotation marks.)
  3. Set targetSdkVersion to 29

Your code should now look something like this:

android {
   // TODO: Step 2.1, Target Android 10.
   compileSdkVersion 29
   buildToolsVersion "29.0.3"
   defaultConfig {
       applicationId "com.example.android.whileinuselocation"
       minSdkVersion 26
       targetSdkVersion 29
       versionCode 1
       versionName "1.0"
   }
...
}

After you do this, you will be asked to sync your project. Click Sync Now.

After that, your app is almost ready for Android 10.

Add Foreground Service Type

In Android 10, you are required to include the type of your foreground service if you need while-in-use location access. In our case, it is being used to get location information.

In the base module, search for TODO: 2.2, Add foreground service type in the AndroidManifest.xml and add the code below to the <service> element:

android:foregroundServiceType="location"

Your code should now look something like this:

<application>
   ...

   <!-- Foreground services in Android 10+ require type. -->
   <!-- TODO: 2.2, Add foreground service type. -->
   <service
       android:name="com.example.android.whileinuselocation.ForegroundOnlyLocationService"
       android:enabled="true"
       android:exported="false"
       android:foregroundServiceType="location" />
</application>

That's it! Your app now supports Android 10 location for "while-in-use" by following the best practices in location for Android.

Run app

Run your app from Android Studio and try the location button.

Everything should work as it did before, but now it works on Android 10. If you didn't accept the permissions for locations before, you should now see the new permission screen!

In this section, you'll target Android 11.

Great news, you don't need to make changes to any files except for the build.gradle file!

Target SDK R

In the base module, search for the TODO: Step 2.1, Target SDK in the the build.gradle file.

Make these changes:

  1. compileSdkVersion to "android-R" (Include the quotation marks.)
  2. targetSdkVersion to "R" (Include the quotation marks.)

Your code should now look something like this:

android {
   // TODO: Step 2.1, Target Android 10.
   compileSdkVersion "android-R"
   buildToolsVersion "29.0.2"
   defaultConfig {
       applicationId "com.example.android.whileinuselocation"
       minSdkVersion 26
       targetSdkVersion "R"
       versionCode 1
       versionName "1.0"
   }
...
}

After you do this, you will be asked to sync your project. Click Sync Now.

After that, your app is ready for Android 11!

Run app

Run your app from Android Studio and try clicking the button.

Everything should work as it did before, but now it works on Android 11. If you didn't accept the permissions for locations before, you should now see the new permission screen!

By checking and requesting location permissions in the ways shown in this codelab, your app can successfully keep track of its access level regarding the device's location.

This page lists a few key best practices related to location permissions. For more information about how to keep your users' data safe, see the permissions best practices guide.

Ask only for the permissions you need

Ask for permissions only when needed. For example:

Support graceful degradation if permission isn't granted

To maintain a good user experience, design your app so that it can gracefully handle the following situations:

Summary

In this step you've learned:

You've learned how to receive location updates in Android, keeping best practices for the platform in mind!

Learn more here: