Cómo conseguir la localización amplia en Android

Imagina que quieres acceder por ejemplo a la región o país en el que se encuentra el usuario de tu App.
Esto que puede parecer sencillo, requiere de unos cuantos pasos.
Damos por hecho que ya has pedido el permiso.
Lo siguiente que tendrás que hacer es recuperar la localización:
1. Recuperar la localización o ubicación con los Play Services
Es la forma más sencilla hoy en día. Necesitarás utilizar el FusedLocationProviderClient
de los Play Services.
Para ello, añade la dependencia a los Play Services:
play-services-location = { group = "com.google.android.gms", name = "play-services-location", version.ref = "playServicesLocation" }
Y en el build.gradle
:
implementation(libs.play.services.location)
Ahora, una vez que tengas otorgado el permiso de COARSE_LOCATION
o FINE_LOCATION
, puedes hacer y, en ciertos casos, comprobado que lo tienes, puedes hacer:
val fusedLocationClient = LocationServices.getFusedLocationProviderClient(ctx)
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
// Aquí ya tienes la ubicación
}
Con esto, puedes acceder a la latitud y longitud (aproximada en el caso de COARSE_LOCATION
) del usuario.
¿Pero y si quieres más datos, como la ciudad o el país donde se encuentra?
2. Recuperar las direcciones del usuario
Para hacer esto, utilizamos el Geocoder
.
Al Geocoder
le pasamos la latitud, la longitud y un máximo de resultados, y nos devolverá un listado de direcciones.
Para crearlo:
val geocoder = Geocoder(context)
Puede devolver varias direcciones si no tiene claro exactamente la dirección a partir de la información, pero para la mayoría de los casos será suficiente con coger el primer elemento devuelto.
Ten en cuenta que esto tampoco sirve para resultados muy detallados, pero para conseguir la ciudad o el país es más que suficiente.
La llamada original, está deprecada:
val addresses = geocoder.getFromLocation(latitude, longitude, maxResults)
Puede hacer llamadas de red, y por tanto bloquear el hilo principal.
Por otro lado, hay una nueva variante con un callback, a partir de TIRAMISU
(API 33):
geocoder.getFromLocation(latitude, longitude, maxResults) { addresses ->
// Usar lista de direcciones
}
Creando una función compat
El único motivo por el que la versión anterior está deprecada es porque se puede llegar a ejecutar en el hilo principal, así que simplemente podrías ignorar el deprecated
y usar la antigua.
No existe, como para otras funciones deprecadas, una versión Compat
que podamos usar directamente.
Si quieres un código a prueba de futuro, te dejo esta función donde, según la versión, se usa la antigua o la nueva.
Además es una función suspend
, para que sea más fácil de usar en un contexto de corrutinas, y que la función antigua se salga del hilo principal:
@Suppress("DEPRECATION")
suspend fun Geocoder.getFromLocationCompat(
@FloatRange(from = -90.0, to = 90.0) latitude: Double,
@FloatRange(from = -180.0, to = 180.0) longitude: Double,
@IntRange maxResults: Int
): List<Address> = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
suspendCancellableCoroutine { continuation ->
getFromLocation(latitude, longitude, maxResults) {
continuation.resume(it)
}
}} else {
withContext(Dispatchers.IO) {
getFromLocation(latitude, longitude, maxResults) ?: emptyList()
}
}
-
Los argumentos tienen anotaciones de rangos. Esto está copiado directamente de la función
getFromLocation
original -
El primer bloque, que llama a la función no deprecada, usa
suspendCancellableCoroutine
, que permite convertir uncallback
en una funciónsuspend
. -
El segundo bloque llama a la función deprecada saliéndose del hilo principal.
Ahora es tan sencillo como hacer:
scope.launch {
val addresses = geocoder.GetFromLocationCompat(lat, lon, 1)
}
3. Obtener la información de la dirección
Si nos ha devuelto alguna dirección, esta tendrá un montón de información sobre la ubicación.
Esta API es muy antigua y no está anotada, por lo que debemos considerar que cualquier valor puede ser null
.
Alguna de la información que trae:
-
countryCode
-
countryName
-
locality
-
postalCode
-
getAddressLine(index)
: la dirección, que puede incluir varias líneas
Aquí vamos a extraer la región (país). Hay que tener en cuenta el posible caso de que cualquier cosa sea nula:
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
val geocoder = Geocoder(context)
scope.launch {
val addresses = geocoder.getFromLocationCompat(
location.latitude,
location.logintude,
1
)
val region = addresses?.firstOrNull()?.countryCode
}
}
Ejemplo de recuperar la región (código de país)
Podríamos hacerlo incluso más sencillo, aprovechando el poder de las corrutinas.
Primero, haríamos una función para recuperar la ubicación mediante una función suspend
, haciendo uso de suspendCancellableCoroutine
:
@SuppressLint("MissingPermission")
suspend fun FusedLocationProviderClient.lastLocation(): Location? {
return suspendCancellableCoroutine { continuation ->
lastLocation.addOnSuccessListener { location ->
continuation.resume(location)
}.addOnFailureListener {
continuation.resume(null)
}
}}
Gracias a la función getFromLocationCompat
y a esta anterior, ahora podemos obtener la región fácilmente a partir de un contexto:
suspend fun Context.getRegion(): String {
val fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
val location = fusedLocationClient.lastLocation()
val geocoder = Geocoder(this)
val addresses = location?.let {
geocoder.getFromLocationCompat(it.latitude, it.longitude, 1)
}
val region = addresses?.firstOrNull()?.countryCode
return region ?: DEFAULT_REGION
}
Finalmente, usando el Effect de pedir permisos:
val ctx = LocalContext.current.applicationContext
val coroutineScope = rememberCoroutineScope()
PermissionRequestEffect(permission = Manifest.permission.ACCESS_COARSE_LOCATION) { granted ->
if (granted) {
coroutineScope.launch {
val region = ctx.getRegion()
}
}
}
Conclusión
Aunque el código es un poco complejo, si lo sigues paso a paso conseguirás llegar al resultado.
Y si quieres aprenderlo todo sobre Jetpack Compose, te animo a que te apuntes a nuestra masterclass:
Cómo conseguir la localización amplia en Android
Cómo pedir permisos en Jetpack Compose