Table of Contents
Overview of Location Services on Android
Location services on Android give your app access to the device location so you can build features like maps, nearby search, geotagging, or location based reminders. Modern Android provides a high level API through Google Play services that combines different location sources such as GPS, Wi Fi, mobile networks, and device sensors into a single, efficient system called Fused Location Provider.
In this chapter you will focus on how to obtain location information in a reliable and battery friendly way, how to choose the right accuracy level, and how to react to location updates in your app. Details about GPS itself and about maps will be covered in separate chapters.
Location access always requires the appropriate permissions and a clear user benefit. You must request location permissions correctly and respect user privacy at all times.
Core Concepts: Providers and the Fused Location Provider
Historically Android exposed multiple location providers such as GPS and network. Modern apps are expected to use the Fused Location Provider API from Google Play services. Instead of forcing you to pick a single source, the fused provider combines all available signals and gives you the best location according to the accuracy and power constraints you request.
The fused provider can use:
GPS satellites for high accuracy, but higher battery cost and worse performance indoors.
Wi Fi and cell towers for medium accuracy with lower battery use.
Bluetooth beacons or other signals when available for fine grained indoor positioning.
You do not manually switch between these. Instead you configure how precise you need the location and how frequently you want updates. The fused provider chooses the best underlying technology.
Setting Up the Fused Location Provider
To work with the Fused Location Provider you add the Google Play services location dependency to your app module. In a Gradle build file this typically looks like:
dependencies {
implementation("com.google.android.gms:play-services-location:21.3.0")
}The version number here is only an example. In a real project you check the latest version in the official documentation or in your existing dependency management setup.
After adding the dependency and syncing your project you can create a fused location client in an Activity or other component that has a Context:
lateinit var fusedLocationClient: FusedLocationProviderClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
}From this client you can request the last known location or continuous location updates, depending on what your feature needs.
Location Permissions and User Privacy
Location is considered sensitive information. Android separates it into two levels of precision: approximate and precise. It also separates permission scopes into foreground and background.
For location services in general you need to understand how these permission types affect what your app receives.
For approximate location the system gives your app a coarse position. This is typically accurate to a few hundred meters. It is often enough for features like local weather or city level content. The corresponding permission is ACCESS_COARSE_LOCATION.
For precise location the system can give a more accurate fix, often within a few meters if GPS is available. This is required for turn by turn navigation or geotagging specific places like photos or check ins. The corresponding permission is ACCESS_FINE_LOCATION.
Foreground location allows your app to access location only while the user is actively using it, for example when your activity is visible or while a foreground service is running. This is the most common pattern for typical user interactions, such as showing nearby items while the user is on a screen.
Background location allows your app to access location even when the user is not interacting with it. This is heavily restricted because of privacy and battery impact. It is only appropriate for use cases that clearly need it, such as persistent fitness tracking or geofencing that works when the app is closed.
On modern Android versions the user may choose whether to grant approximate or precise access when your app requests fine location. When your app asks for ACCESS_FINE_LOCATION the user can still decide to only allow approximate access. Your code must handle this gracefully and adapt the feature quality if only coarse location is available.
Checking and Requesting Location Permissions
Before you use location services you must check whether the required permission has been granted. If not, you request it at runtime and handle the user decision.
To check a permission you use ContextCompat.checkSelfPermission. If the result is PackageManager.PERMISSION_GRANTED you can access location. Otherwise you must call requestPermissions from an Activity or the corresponding API with the Activity Result API.
Here is a compact example that illustrates the check and request pattern for fine location:
private val LOCATION_REQUEST_CODE = 1001
private fun checkLocationPermission() {
val permission = Manifest.permission.ACCESS_FINE_LOCATION
if (ContextCompat.checkSelfPermission(this, permission)
== PackageManager.PERMISSION_GRANTED
) {
startUsingLocation()
} else {
ActivityCompat.requestPermissions(
this,
arrayOf(permission),
LOCATION_REQUEST_CODE
)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == LOCATION_REQUEST_CODE) {
if (grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED
) {
startUsingLocation()
} else {
handleLocationPermissionDenied()
}
}
}This code uses the legacy callback style for clarity. In modern apps you will often prefer the Activity Result API for better structure, but the underlying logic is the same. You first check, then request if needed, then react to the user decision.
When a user denies the permission permanently the system stops showing the permission dialog. In that case your app should explain why the feature is unavailable and can optionally provide a way to open the app settings screen to change permissions manually.
Never try to bypass user permission choices. If the user denies location access your app must still function as well as possible without it or clearly inform the user why some features are disabled.
Last Known Location vs Continuous Updates
There are two common ways to obtain location from the fused provider: a single snapshot of the last known location or a continuous stream of updates.
The last known location is a quick, cached value that the system already has from previous requests or from other apps. It is fast and uses no extra power, but it can be outdated or null if the device has not determined a location recently.
To get the last known location you call getLastLocation and handle a potential null result:
@SuppressLint("MissingPermission")
private fun fetchLastKnownLocation() {
fusedLocationClient.lastLocation
.addOnSuccessListener { location: Location? ->
if (location != null) {
useLocation(location)
} else {
requestFreshLocation()
}
}
}
The @SuppressLint("MissingPermission") annotation is only appropriate if you have already verified that the permission is granted before this call. This is important because the fused provider still requires the correct permission check.
Continuous location updates are required when your feature needs changes over time, such as tracking movement on a map or recording a route. For this you create a LocationRequest, define how often and how accurate updates should be, and then register a LocationCallback.
Configuring LocationRequest for Accuracy and Power
Battery usage is a key concern with location. A high accuracy, frequent update request that uses GPS all the time can drain the battery quickly. The LocationRequest class lets you tell the system how accurate and how frequent your updates should be so the fused provider can balance quality and power use.
In current APIs you typically create a LocationRequest with a builder:
val locationRequest = LocationRequest.Builder(
Priority.PRIORITY_HIGH_ACCURACY,
5_000L // 5 seconds
).setMinUpdateDistanceMeters(10f)
.build()Here the priority and interval are important knobs:
Priority.PRIORITY_HIGH_ACCURACY tells the system you prefer precise location. The fused provider will use GPS when appropriate.
Priority.PRIORITY_BALANCED_POWER_ACCURACY uses less power, usually by favoring Wi Fi and cell towers. It is a good default for many apps.
Priority.PRIORITY_LOW_POWER and Priority.PRIORITY_PASSIVE are more specialized. Low power is useful when precise position is not critical. Passive lets you receive updates only when other apps request location, which saves power.
The update interval, here 5 seconds, is a suggestion rather than a strict schedule. The system may deliver updates less frequently depending on conditions. You can also set a minimum distance so that small movements do not trigger updates. This is useful for tracking movement without spamming your app when the device is mostly stationary.
Always choose the lowest accuracy and least frequent updates that still satisfy your feature requirements. This protects battery life and user trust.
Subscribing to Location Updates
To receive continuous updates you create a LocationCallback and register it with the fused client. The callback runs on a specified thread or looper and receives LocationResult objects.
A typical setup inside an Activity could look like this:
private lateinit var locationCallback: LocationCallback
@SuppressLint("MissingPermission")
private fun startLocationUpdates() {
val locationRequest = LocationRequest.Builder(
Priority.PRIORITY_BALANCED_POWER_ACCURACY,
10_000L
).build()
locationCallback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult) {
val location = result.lastLocation ?: return
handleLocationUpdate(location)
}
}
fusedLocationClient.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
}
The Looper.getMainLooper() parameter tells the system to deliver the callback on the main thread. This is convenient when you want to update the UI directly from the callback, for example by moving a marker on a map. For long running background work you would move processing to a background thread.
Since location updates can continue as long as requested, you must explicitly stop them when they are no longer needed.
Stopping Location Updates and Using the Lifecycle
If you forget to stop location updates your app may continue consuming power even after the user leaves the relevant screen. The basic pattern is to start updates when the user starts interacting with a feature and stop them when the user leaves or pauses that feature.
You stop updates by calling removeLocationUpdates with the same callback instance:
private fun stopLocationUpdates() {
if (::locationCallback.isInitialized) {
fusedLocationClient.removeLocationUpdates(locationCallback)
}
}
In an Activity that shows location on screen, a common pattern is to start updates in onResume and stop them in onPause. In components that follow lifecycle aware patterns you can use architecture components to manage subscriptions with less manual code, which will be covered later in the course.
Foreground, Background, and Location Services
Location services behave differently depending on whether your app is in the foreground. While the activity is visible you can usually obtain location with the foreground permissions described earlier. When your app moves to the background, restrictions apply. The system may stop updates or require a foreground service with a persistent notification to continue receiving them.
For long running background tracking, such as fitness apps that record a route during a workout, you often combine the fused provider with a foreground Service. The service displays a notification so the user knows that location is in use, and the system gives it higher priority to keep it running.
Background location has specific permission and policy requirements that are stricter than simple foreground use. You must justify this access to users and to the Play Store review process. If your feature does not absolutely require background tracking, it is better to keep location use tied to visible UI.
Handling Common Location Scenarios
Real world location use must deal with imperfect conditions. The device may be indoors where GPS is weak, the user may move through areas with poor connectivity, or permissions may change at runtime.
You should handle cases such as missing location gracefully. The last known location can be null. Continuous updates may take time to deliver the first fix. In these situations you can show a loading state, fall back to approximate features, or let the user know that the device is still searching for a location.
Location precision can vary as the user moves. You can inspect fields such as location.accuracy which tells you an estimated radius in meters around the reported coordinates. Some features, such as navigation, may require you to ignore updates that are too imprecise, for example by discarding locations where the accuracy value is above a certain threshold.
The user might revoke location permission while your app is in use. On modern Android versions this can happen at any time through the quick settings panel or system settings. When location access disappears you must stop using the location APIs and update your UI, perhaps by disabling map controls that depend on current position and prompting the user to grant permission again if they try to use those features.
Testing and Simulating Location
Testing location features can be challenging because physical movement is not always practical during development. Android and Android Studio provide tools to simulate location so you can verify behavior in predictable scenarios.
In the emulator you can send mock locations using the Extended Controls panel. You can manually enter coordinates or play routes from GPX or KML files. Your app receives these locations through the same fused provider calls, which lets you test routes, geofences, and movement based UI.
On physical devices you can enable a mock location app in developer options. You then create a simple test app or use dedicated tools that send synthetic locations. Your main app will read these as if they were real. This is helpful for debugging behavior in cities or areas you cannot physically visit.
By combining real world testing, emulator routes, and mock data, you can validate that your app handles both smooth movement and edge cases like signal loss or abrupt jumps.
Summary
Location services on Android revolve around the Fused Location Provider, which merges multiple underlying technologies into a single API that balances accuracy and power. To use it correctly you configure appropriate permissions, choose between last known locations and continuous updates, and adjust accuracy and frequency to your actual needs.
Proper management of location callbacks and lifecycle prevents unnecessary battery drain, while careful handling of permissions and states protects user privacy and keeps your app compatible with modern platform requirements. With these foundations in place you are ready to explore more specific topics like using raw GPS data and integrating maps and geolocation features in later chapters.