Get the Current Location and Geocode It
Android applications often need to know where a user is before they can display a nearby shop, tag a photo, or fill in a delivery address. The operating system can combine signals from GPS, Wi-Fi, mobile networks, and sensors, although GPS alone is rarely a complete solution.
A location fix is more than a pair of latitude and longitude values. It also carries an accuracy estimate, a timestamp, and sometimes speed or altitude. Reverse geocoding can turn those coordinates into a street address, suburb, state, or country that people recognise.
The sample below uses Kotlin, Android Studio, and Google Play services’ fused location provider. It requests a single foreground fix, checks permission state, and sends the resulting coordinates to Android’s Geocoder without requesting background location access.
A physical phone remains the best test because GPS reception and geocoder services vary by device. The workflow also recognises Australian cities, regional areas, address conventions, and privacy considerations under the Privacy Act 1988 and the Australian Privacy Principles.
Add the location dependencies
Add the Play services location library to the app module’s build.gradle.kts file. Check the dependency page for the current stable release before applying it.
dependencies {
implementation("com.google.android.gms:play-services-location:21.3.0")
}
Declare precise and approximate location permissions in the app manifest. The coarse permission supports an approximate fix, while the fine permission allows precise positioning.
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Request both permissions at runtime with ActivityResultContracts.RequestMultiplePermissions. Treat coarse access as sufficient when that is all the user grants.
private val locationPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { grants ->
val granted =
grants[Manifest.permission.ACCESS_FINE_LOCATION] == true ||
grants[Manifest.permission.ACCESS_COARSE_LOCATION] == true
if (granted) fetchCurrentLocation()
else showLocationPermissionState()
}
private fun requestLocationAccess() {
locationPermissionLauncher.launch(
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
)
)
}
Android 12 and later can offer an approximate-location choice in the permission dialog. If access is denied, show an explanation and an option to open system settings rather than repeatedly prompting. A foreground-only feature does not need ACCESS_BACKGROUND_LOCATION, and requesting background access during startup would weaken the consent flow.
Request a fresh device position
The fused provider combines available signals and can return a recent position without waiting for a new GPS satellite lock. Create it from LocationServices, select a suitable priority, and handle both a null fix and a task failure.
private var locationTokenSource: CancellationTokenSource? = null
@SuppressLint("MissingPermission")
private fun fetchCurrentLocation() {
val fineGranted = ContextCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
val coarseGranted = ContextCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED
if (!fineGranted && !coarseGranted) {
showLocationPermissionState()
return
}
val priority = if (fineGranted) {
Priority.PRIORITY_HIGH_ACCURACY
} else {
Priority.PRIORITY_BALANCED_POWER_ACCURACY
}
val client = LocationServices.getFusedLocationProviderClient(this)
val source = CancellationTokenSource()
locationTokenSource = source
client.getCurrentLocation(priority, source.token)
.addOnSuccessListener { location ->
if (location == null) showLocationUnavailable()
else renderLocation(location)
}
.addOnFailureListener { showLocationUnavailable() }
}
Keep the cancellation source as a field and call locationTokenSource?.cancel() when the Activity closes. If the request fails, query lastLocation as a fallback, but inspect its timestamp first because an old cached fix can place someone at the wrong end of a street.
Before starting, check whether location services are enabled and offer a link to Settings.ACTION_LOCATION_SOURCE_SETTINGS. Dense parts of Sydney and Melbourne can weaken GPS signals, while regional and remote areas may have sparse mobile coverage, making a clear-sky fix slower. Devices without Google Play services can use the platform LocationManager with GPS and network providers instead.
Validate accuracy and freshness
Location.accuracy is the expected radius in metres around the reported coordinates. Check hasAccuracy() before presenting a position, and label a 500-metre result as an approximate area rather than an exact address. Around 20 to 50 metres may be useful for a street-level lookup, while a few hundred metres is often suitable only for showing the nearest suburb.
Use the fix time to prevent stale data from looking current. Location.getElapsedRealtimeAge() gives an age based on the monotonic device clock, which avoids errors when the wall clock changes. Set an age limit appropriate to the task and request another fix when that limit is exceeded.
Avoid reverse-geocoding every movement or sensor update. For a one-time lookup, wait for an acceptable fix, geocode it once, and retain only the fields the screen needs. If no address is returned, display the coordinates and accuracy instead of inventing a street-level result.
Turn coordinates into an address
Android’s Geocoder performs reverse geocoding: it accepts latitude and longitude and returns candidate Address objects. Construct it with Locale("en", "AU") when the interface should use English Australian formatting.
On Android 13 and later, use the asynchronous listener overload. The older synchronous call can serve as a compatibility branch, but it should run away from the main thread.
private fun reverseGeocode(location: Location) {
val geocoder = Geocoder(this, Locale("en", "AU"))
if (!geocoder.isPresent) {
renderCoordinatesOnly(location)
return
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
geocoder.getFromLocation(
location.latitude,
location.longitude,
5,
object : Geocoder.GeocodeListener {
override fun onGeocode(addresses: MutableList<Address>) {
renderAddresses(addresses)
}
override fun onError(errorMessage: String?) {
renderCoordinatesOnly(location)
}
}
)
} else {
lifecycleScope.launch {
val addresses = withContext(Dispatchers.IO) {
@Suppress("DEPRECATION")
geocoder.getFromLocation(
location.latitude,
location.longitude,
5
)
}
renderAddresses(addresses.orEmpty())
}
}
}
Read each result according to its purpose. addressLine can contain a street and unit number, locality usually contains the suburb or town, and adminArea contains the state or territory. Australian results may use “NSW”, “VIC”, or “QLD”, while rural localities and “via” routes may not match a postal address format. The postcode helps group results, but it does not prove that the GPS point is the property’s exact entrance.
Geocoding can fail when the device is offline, the service has no match, or the device lacks a usable geocoder backend. Keep the coordinates as a fallback. A production service can also call the Google Maps Geocoding API, usually through a secured backend, when consistent address data and quotas are required.
Build a reliable user workflow
Represent the screen as explicit states: permission required, location services disabled, loading, coordinates found, address found, and unavailable. This prevents a spinner from running forever when the user declines access or the request times out.
A one-off check should request a single fix. Continuous tracking is a different feature with extra battery, notification, and background-access requirements. Release listeners, cancel pending work in onDestroy, and refresh only while the relevant foreground screen is active.
For offline continuity, cache a coordinate, accuracy, capture time, and optional address in SharedPreferences or a database. SharedPreferences is not encrypted by default, so avoid keeping a movement history there and delete retained location data when the account ends. For a covered Australian organisation, explain the purpose and retention period in the privacy notice and follow the applicable Australian Privacy Principles.
Test on a physical handset with location enabled, location disabled, permission denied, approximate permission, weak GPS, and no network connection. Use emulator coordinates such as central Sydney or Melbourne, then compare the result with the address formats familiar from Google Maps and local delivery apps. Repeated editor operations can be packaged as commands; the same broad idea appears in automating Magik macros, while Android permission and lifecycle rules still require explicit handling.