Receive location updates in Android 10 with Kotlin

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

When an app running on Android 10 requests location access, users have three options:

  • Allow all the time (foreground and background)
  • While in use (foreground only)
  • Deny

In this codelab, you'll learn how to quickly support location in Android 10. At the end of the codelab, you can expect to have an app that supports foreground-only and foreground + background location (following best practices for while-in-use and all-the-time location access).

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 and some familiarity with location services.

What you'll learn

  • Best practices for location in Android 10
  • How to handle foreground only location permissions
  • How to handle foreground and background location permissions

What you'll build

You will modify a pre-existing location app to support Android 10 by:

  • Adding support for foreground-only location, or while-in-use access.
  • Adding support for foreground and background location, or all-the-time access.

What you'll need

  • Android Studio 3.5 or later to run the code
  • A device/emulator running a developer preview of Android 10

The app has a simple UI:

  • Support for foreground only location updates (by tapping the first button).
  • Support foreground and background location updates (by tapping the second button).
  • Information is displayed below the buttons.

Concepts and setup

Concepts

The focus of this codelab is to show you what is different in Android 10 and how you can support it. Most of the code related to tracking location is already completed for you, as this hasn't changed much.

Before completing this codelab, you should have a basic understanding of location in Android. That said, it doesn't hurt to review the basics.

The Basics

Android gives your applications access to the location services supported by the device through classes in the android.location package. The central component of the location framework is the LocationManager system service, which provides APIs to determine location and bearing of the underlying device (if available).

In order to receive location updates from 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 will fail at runtime when requesting location updates.

Android 10 augments this by allowing users to choose when you have access to this information ("while in use", "all the time", "not at all").

To support the additional location control, Android 10 introduces a new location permission, ACCESS_BACKGROUND_LOCATION.

Unlike the existing ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION permissions, the new permission only affects an app's access to location when it's running in the background. An app is considered to be in the background unless one of its activities is visible or the app is running a foreground service.

If your app has a foreground service (along with a notification), then you don't have to request background location access, but you will still need to declare that your foreground service has a foreground service type of "location".

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

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

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 a spinner in the status bar at the bottom of Android Studio:

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

In addition, 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 support location in Android 10. We'll be using the base module, which is the starting point for adding foreground-only or "while-in-use" location support. In other words, you'll add code from each step to base.

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

Overview of key components:

  • MainActivity: UI for user to start/stop tracking location (foreground only and foreground + background).
  • LocationService: Service that tracks foreground-only location and promotes itself to foreground service (with a notification) if app goes into background and location tracking is enabled.
  • Util: Adds extension functions for the Location class and saves location in SharedPreferences (simplified data layer).

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. (Remember, this only works for Android 10+ devices.)
  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 application below:

  2. Feel free to play with location buttons. The app is already functional and provides location tracking for foreground and foreground + background location. We'll now enable the app to work on Android 10.

In this section, you'll target Android 10 and add support for foreground-only location tracking, or "while-in-use" access.

What does foreground only mean? It means that the app is tracking location while it is in the foreground. That means either an activity is in the foreground or that the app has a service running in the foreground (with a notification).

Our app already has a feature that only tracks the user using an activity or service running in the foreground, so there isn't a lot of work to do. Your app might be similar, as this is a best practice for getting frequent location updates.

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 29 in the build.gradle file.

Make these changes:

  1. compileSdkVersion to 29
  2. buildToolsVersion to "29.0.2" (with the quotation marks)
  3. targetSdkVersion to 29

Your code should now look something like this:

android {
   // TODO: Step 2.1, Target Android 10.
   compileSdkVersion 29
   buildToolsVersion "29.0.2"
   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 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 track location.

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 just following the best practices in location for Android.

Run app

Run your app from Android Studio and try the foreground only 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 add support for retrieving location from the background in Android 10. You should generally prefer foreground-only location support, but there are some use cases where you do need location in the background, e.g., geofencing.

Add the background permission in the manifest

Android 10 includes a new permission to access location in background that you must add to your manifest.

In the base module, search for TODO: 3.1, Add background uses-permission to manifest in the AndroidManifest.

Add the new <uses-permission> element below the comment:

<!-- TODO: 3.1, Add background uses-permission to manifest. -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

You are now allowed to ask for all-the-time access to device location, including in the background. Let's add the checks and requests for that permission now.

Add code check for Android 10

In the base module, search for Step 3.2, add check for devices with Android 10 in the MainActivity.kt. and replace the current version with the code below.

// TODO: Step 3.2, add check for devices with Android 10.
private val runningQOrLater =
   android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q

We'll be adding code to check and request the new background location permission. However, this code is not required unless you are running on Android 10 or higher.

Now you have a way to include these new checks/requests in Android 10 only. Let's get started with adding some code.

Add check for access to background location

In the MainActivity.kt, search for TODO: Step 3.3, Add permission check for background permission and replace the line directly below the comment with this code:

// TODO: Step 3.3, Add check for background permission.
val backgroundPermissionApproved =
   if (runningQOrLater) {
       ActivityCompat.checkSelfPermission(
           this, Manifest.permission.ACCESS_BACKGROUND_LOCATION
       ) == PackageManager.PERMISSION_GRANTED
   } else {
       true
   }

If you are used to checking permissions in Android, this should look very familiar. The only difference is you are now checking the ACCESS_BACKGROUND_LOCATION permission.

We are using some Kotlin magic to do the following:

  • Check if we are running Android 10 or not (from our last step).
  • If we are, check the permission as usual.
  • If we are not, then we return true, as there isn't a permission for background. location in Android prior to 10.

You can see in the following line (already included in project), that we simply use the && operator on the foreground (ACCESS_FINE_LOCATION) and the background (ACCESS_BACKGROUND_LOCATION) location permissions to determine whether an app has all-the-time access to device location.

Now we know whether the user has approved accessing location in the background, but we still need to allow the user to actually approve access.

Add request background location access

In the MainActivity.kt, search for TODO: Step 3.4, Add another entry to permission request array and add the code below:

// TODO: Step 3.4, Add another entry to permission request array.
if (runningQOrLater) {
   permissionRequests.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
}

If you look at the full block now, it should look like this:

val permissionRequests = arrayListOf(Manifest.permission.ACCESS_FINE_LOCATION)
// TODO: Step 3.4, Add another entry to permission request array.
if (runningQOrLater) {
   permissionRequests.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
}

You can see from the first line, we are initializing an ArrayList to pass to ActivityCompat.requestPermissions().

In our new code, we check whether we are running on Android 10, and if we are, we simply add another entry to the ArrayList for access to the location in the background.

This will trigger the user to choose the level of access they want: either while-in-use access (foreground location permission only) or all-the-time access (foreground and background location permissions).

One other note, ActivityCompat.requestPermissions() doesn't actually take an ArrayList, so we need to convert it to an actual Array with toTypedArray().

Check the background location was approved in onRequestPermissionsResult()

We're almost there!

In the MainActivity.kt again, search for TODO: Step 3.5, For Android 10, check if background permissions approved in request code and add this block of code:

// TODO: Step 3.5, For Android 10, check if background permissions approved in request code.
if(runningQOrLater) {
   foregroundAndBackgroundLocationApproved =
       foregroundAndBackgroundLocationApproved &&
               (grantResults[1] == PackageManager.PERMISSION_GRANTED)
}


Here we are checking if we are running on Android 10. If we are, we check that the second permission was approved. If you recall from our previous step, the second item in our array was ACCESS_BACKGROUND_LOCATION.

Since we are already checking the first result in our code for Android 9 or lower, we can piggyback on that by just reusing the same variable, foregroundAndBackgroundLocationApproved, and && it with our new permission. After all, we can't say it was approved unless both the permissions were granted.

The full code should look like this:

var foregroundAndBackgroundLocationApproved =
   grantResults[0] == PackageManager.PERMISSION_GRANTED

// TODO: Step 3.5, For Android 10, check if background permissions approved in request code.
if(runningQOrLater) {
   foregroundAndBackgroundLocationApproved =
       foregroundAndBackgroundLocationApproved &&
               (grantResults[1] == PackageManager.PERMISSION_GRANTED)
}

Review call to location in foreground and background

No code to add in this step. We just want to review one more line.

Search for TODO: Step 3.6, review method call for foreground and background location which is just a couple lines below the code you just added.

This is just the call to start tracking location after we've verified that all permissions were granted.

// TODO: Step 3.6, review method call for foreground and background location.
foregroundAndBackgroundLocationApproved ->
   startForegroundAndBackgroundLocation()

OK, you are done!

Now let's try out our app. Remember, if you got lost along the way, have a look at the complete module. That has all the completed/working code.

Run app

Your app now fully supports Android 10 location. Let's try it out!

Run your app from Android Studio. Now, when you click on access location in the foreground and background you should see one of the following screens, depending on which permissions are approved:

You can see after the permissions are granted, your app starts showing updates.

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

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:

  • Don't request a location permission at app startup unless absolutely necessary.
  • If your app targets Android 10 and needs access to location information only when running in the foreground, don't request ACCESS_BACKGROUND_LOCATION.

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:

  • Your app doesn't have any access to location information.
  • Your app doesn't have access to location information when running in the background.

Summary

In this step you've learned:

  • Best practices for location in Android

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

Learn more here: