Convert User’s Latitude and Longitude To Human Readable Format…
Photo by henry perks on Unsplash
I was working on a project that required me to get the user’s location in a human-readable form and display it back to the user.
After going through a lot of Google search pages, I couldn’t find a code that worked perfectly for me, so I decided to write the code implementation myself and also derive inspiration from other legacy methods I found online.
After I have written and tested the code implementation that works well for me, I have broken it down into three different methods to make it easier to understand. Below, I’ll explain each method in details…
Fetching User Location…
/** | |
* Manages all location related tasks for the app. | |
*/ | |
//A callback for receiving notifications from the FusedLocationProviderClient. | |
lateinit var locationCallback: LocationCallback | |
//The main entry point for interacting with the Fused Location Provider | |
lateinit var locationProvider: FusedLocationProviderClient | |
@SuppressLint("MissingPermission") | |
@Composable | |
fun getUserLocation(context: Context): LatandLong { | |
// The Fused Location Provider provides access to location APIs. | |
locationProvider = LocationServices.getFusedLocationProviderClient(context) | |
var currentUserLocation by remember { mutableStateOf(LatandLong()) } | |
DisposableEffect(key1 = locationProvider) { | |
locationCallback = object : LocationCallback() { | |
//1 | |
override fun onLocationResult(result: LocationResult) { | |
/** | |
* Option 1 | |
* This option returns the locations computed, ordered from oldest to newest. | |
* */ | |
for (location in result.locations) { | |
// Update data class with location data | |
currentUserLocation = LatandLong(location.latitude, location.longitude) | |
Log.d(LOCATION_TAG, "${location.latitude},${location.longitude}") | |
} | |
/** | |
* Option 2 | |
* This option returns the most recent historical location currently available. | |
* Will return null if no historical location is available | |
* */ | |
locationProvider.lastLocation | |
.addOnSuccessListener { location -> | |
location?.let { | |
val lat = location.latitude | |
val long = location.longitude | |
// Update data class with location data | |
currentUserLocation = LatandLong(latitude = lat, longitude = long) | |
} | |
} | |
.addOnFailureListener { | |
Log.e("Location_error", "${it.message}") | |
} | |
} | |
} | |
//2 | |
if (hasPermissions( | |
context, | |
Manifest.permission.ACCESS_FINE_LOCATION, | |
Manifest.permission.ACCESS_COARSE_LOCATION | |
) | |
) { | |
locationUpdate() | |
} else { | |
askPermissions( | |
context, REQUEST_LOCATION_PERMISSION, Manifest.permission.ACCESS_FINE_LOCATION, | |
Manifest.permission.ACCESS_COARSE_LOCATION | |
) | |
} | |
//3 | |
onDispose { | |
stopLocationUpdate() | |
} | |
} | |
//4 | |
return currentUserLocation | |
} | |
//data class to store the user Latitude and longitude | |
data class LatandLong( | |
val latitude: Double = 0.0, | |
val longitude: Double = 0.0 | |
) |
The above code is simple and self-explanatory, but some parts need explanation, and I have numbered them.
- Inside the Location Callback Object, I gave two option to get the user location, each with their respective explanation
- This part of the code check if our app has the required permissions; if the permission is available, request LocationUpdate(Code will be provided below) else ask for the permissions. Check out how to request multiple permission in jetpack compose here
- Here, we call the onDispose method of DisposableEffect to stop requesting location when we are done.
- We return the requested Latitude and Longitude
Request Location Update
@SuppressLint("MissingPermission") | |
fun locationUpdate() { | |
locationCallback.let { | |
//An encapsulation of various parameters for requesting | |
// location through FusedLocationProviderClient. | |
val locationRequest: LocationRequest = | |
LocationRequest.create().apply { | |
interval = TimeUnit.SECONDS.toMillis(60) | |
fastestInterval = TimeUnit.SECONDS.toMillis(30) | |
maxWaitTime = TimeUnit.MINUTES.toMillis(2) | |
priority = LocationRequest.PRIORITY_HIGH_ACCURACY | |
} | |
//use FusedLocationProviderClient to request location update | |
locationProvider.requestLocationUpdates( | |
locationRequest, | |
it, | |
Looper.getMainLooper() | |
) | |
} | |
} |
This is also self-explanatory, so we don’t have to waste any more time explaining it. What it does is create Location Request parameters to pass to the request. LocationUpdate of FusedLocationProviderClient
Stop Location Update…
fun stopLocationUpdate() { | |
try { | |
//Removes all location updates for the given callback. | |
val removeTask = locationProvider.removeLocationUpdates(locationCallback) | |
removeTask.addOnCompleteListener { task -> | |
if (task.isSuccessful) { | |
Log.d(LOCATION_TAG, "Location Callback removed.") | |
} else { | |
Log.d(LOCATION_TAG, "Failed to remove Location Callback.") | |
} | |
} | |
} catch (se: SecurityException) { | |
Log.e(LOCATION_TAG, "Failed to remove Location Callback.. $se") | |
} | |
} |
Job Offers
This method stops all Location Update request to avoid unnecessary use of resources.
Conclusion…
With the three methods above we can easily request the user’s longitude and latitudes, but what if we want a human-readable location; This can be done by using the GeoLocation API provided by the Android Framework; Below is a simple way to archive that…
fun getReadableLocation(latitude: Double, longitude: Double, context: Context): String { | |
var addressText = "" | |
val geocoder = Geocoder(context, Locale.getDefault()) | |
try { | |
val addresses = geocoder.getFromLocation(latitude, longitude, 1) | |
if (addresses?.isNotEmpty() == true) { | |
val address = addresses[0] | |
addressText = "${address.getAddressLine(0)}, ${address.locality}" | |
// Use the addressText in your app | |
Log.d("geolocation", addressText) | |
} | |
} catch (e: IOException) { | |
Log.d("geolocation", e.message.toString()) | |
} | |
return addressText | |
} |
Full Code…
Below is the full code implementation
/** | |
* Manages all location related tasks for the app. | |
*/ | |
//A callback for receiving notifications from the FusedLocationProviderClient. | |
lateinit var locationCallback: LocationCallback | |
//The main entry point for interacting with the Fused Location Provider | |
lateinit var locationProvider: FusedLocationProviderClient | |
@SuppressLint("MissingPermission") | |
@Composable | |
fun getUserLocation(context: Context): LatandLong { | |
// The Fused Location Provider provides access to location APIs. | |
locationProvider = LocationServices.getFusedLocationProviderClient(context) | |
var currentUserLocation by remember { mutableStateOf(LatandLong()) } | |
DisposableEffect(key1 = locationProvider) { | |
locationCallback = object : LocationCallback() { | |
override fun onLocationResult(result: LocationResult) { | |
/** | |
* Option 1 | |
* This option returns the locations computed, ordered from oldest to newest. | |
* */ | |
for (location in result.locations) { | |
// Update data class with location data | |
currentUserLocation = LatandLong(location.latitude, location.longitude) | |
Log.d(LOCATION_TAG, "${location.latitude},${location.longitude}") | |
} | |
/** | |
* Option 2 | |
* This option returns the most recent historical location currently available. | |
* Will return null if no historical location is available | |
* */ | |
locationProvider.lastLocation | |
.addOnSuccessListener { location -> | |
location?.let { | |
val lat = location.latitude | |
val long = location.longitude | |
// Update data class with location data | |
currentUserLocation = LatandLong(latitude = lat, longitude = long) | |
} | |
} | |
.addOnFailureListener { | |
Log.e("Location_error", "${it.message}") | |
} | |
} | |
} | |
if (hasPermissions( | |
context, | |
Manifest.permission.ACCESS_FINE_LOCATION, | |
Manifest.permission.ACCESS_COARSE_LOCATION | |
) | |
) { | |
locationUpdate() | |
} else { | |
askPermissions( | |
context, REQUEST_LOCATION_PERMISSION, Manifest.permission.ACCESS_FINE_LOCATION, | |
Manifest.permission.ACCESS_COARSE_LOCATION | |
) | |
} | |
onDispose { | |
stopLocationUpdate() | |
} | |
} | |
// | |
return currentUserLocation | |
} | |
fun stopLocationUpdate() { | |
try { | |
//Removes all location updates for the given callback. | |
val removeTask = locationProvider.removeLocationUpdates(locationCallback) | |
removeTask.addOnCompleteListener { task -> | |
if (task.isSuccessful) { | |
Log.d(LOCATION_TAG, "Location Callback removed.") | |
} else { | |
Log.d(LOCATION_TAG, "Failed to remove Location Callback.") | |
} | |
} | |
} catch (se: SecurityException) { | |
Log.e(LOCATION_TAG, "Failed to remove Location Callback.. $se") | |
} | |
} | |
@SuppressLint("MissingPermission") | |
fun locationUpdate() { | |
locationCallback.let { | |
//An encapsulation of various parameters for requesting | |
// location through FusedLocationProviderClient. | |
val locationRequest: LocationRequest = | |
LocationRequest.create().apply { | |
interval = TimeUnit.SECONDS.toMillis(60) | |
fastestInterval = TimeUnit.SECONDS.toMillis(30) | |
maxWaitTime = TimeUnit.MINUTES.toMillis(2) | |
priority = LocationRequest.PRIORITY_HIGH_ACCURACY | |
} | |
//use FusedLocationProviderClient to request location update | |
locationProvider.requestLocationUpdates( | |
locationRequest, | |
it, | |
Looper.getMainLooper() | |
) | |
} | |
} | |
data class LatandLong( | |
val latitude: Double = 0.0, | |
val longitude: Double = 0.0 | |
) | |
fun getReadableLocation(latitude: Double, longitude: Double, context: Context): String { | |
var addressText = "" | |
val geocoder = Geocoder(context, Locale.getDefault()) | |
try { | |
val addresses = geocoder.getFromLocation(latitude, longitude, 1) | |
if (addresses?.isNotEmpty() == true) { | |
val address = addresses[0] | |
addressText = "${address.getAddressLine(0)}, ${address.locality}" | |
// Use the addressText in your app | |
Log.d("geolocation", addressText) | |
} | |
} catch (e: IOException) { | |
Log.d("geolocation", e.message.toString()) | |
} | |
return addressText | |
} |
Thanks for hanging on. I hope you found this information helpful and educational. If there’s anything you’re curious about or if I missed anything important, please don’t hesitate to reach out and ask!
I’m here to help, and I promise to respond to every comment. Let’s keep the conversation going!
Until next time, I’m Jnr. Bye for now! 🖤
This article was previously published on proandroiddev.com