Android Location Provider — Complete Guide
In this tutorial, you'll learn about Android Location Provider. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
The Problem
Your location updates are inaccurate (off by 500m), or you get null location from the Fused Location Provider.
Wrong Approach ❌
// Using coarse location only
val locationRequest = LocationRequest.create().apply {
priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY // ~100m accuracy
}
// Getting last known location without checking recency
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
// location may be hours old!
}
Output: Low accuracy. Stale location returned.
Right Approach ✅
class LocationManager(private val context: Context) {
private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
// High accuracy location request
fun getHighAccuracyLocation(): Task<Location> {
val locationRequest = LocationRequest.create().apply {
priority = LocationRequest.PRIORITY_HIGH_ACCURACY // GPS required
interval = 10000 // 10 seconds
fastestInterval = 5000
smallestDisplacement = 10f // 10 meters
}
return fusedLocationClient.requestLocationUpdates(
locationRequest,
createLocationCallback(),
Looper.getMainLooper()
).continueWith { task ->
// Return current location after first update
fusedLocationClient.lastLocation.result
}
}
// Get last known location with recency check
suspend fun getFreshLocation(): Location? {
val location = fusedLocationClient.lastLocation.await()
// Check if location is recent enough
if (location != null) {
val age = System.currentTimeMillis() - location.time
if (age < 5 * 60 * 1000) { // Less than 5 minutes old
return location
}
}
// Request a fresh location update
return requestSingleUpdate()
}
private suspend fun requestSingleUpdate(): Location? {
val locationRequest = LocationRequest.create().apply {
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
numUpdates = 1 // Single update
interval = 0
fastestInterval = 0
}
return suspendCancellableCoroutine { cont ->
val callback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult) {
cont.resume(result.lastLocation)
}
override fun onLocationAvailability(availability: LocationAvailability) {
if (!availability.isLocationAvailable) {
cont.resume(null)
}
}
}
fusedLocationClient.requestLocationUpdates(locationRequest, callback, Looper.getMainLooper())
.addOnFailureListener { cont.resume(null) }
cont.invokeOnCancellation {
fusedLocationClient.removeLocationUpdates(callback)
}
}
}
// Geocoding (reverse geocode)
suspend fun getAddress(lat: Double, lng: Double): String? {
val geocoder = Geocoder(context, Locale.getDefault())
return geocoder.getFromLocation(lat, lng, 1)?.firstOrNull()?.getAddressLine(0)
}
}
Output: Accurate location with recency checks.
Prevention
- Use
PRIORITY_HIGH_ACCURACYwhen GPS accuracy is needed. - Check location timestamp for recency before trusting
lastLocation. - Use
numUpdates = 1for a single fresh location request. - Request location permission before using FusedLocationProvider.
- Fall back to
PRIORITY_BALANCED_POWER_ACCURACYwhen GPS is disabled.
Common Mistakes with location provider
- Using
headandtailinstead of pattern matching, causing runtime errors on empty lists - Forgetting that lazy evaluation defers computation until the value is forced, causing space leaks with unevaluated thunks
- Using
returnto exit a function early instead of wrapping a pure value in the monad
These mistakes appear frequently in real-world Android code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.
Practice Exercise
Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.
This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.
FAQ
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro