Cómo conseguir la localización amplia en Android

Avatar
5 min lectura

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 FusedLocationProviderClientde 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" }
YAML

Y en el build.gradle:

implementation(libs.play.services.location)
Kotlin

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
}
Kotlin

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)
Kotlin

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)
Kotlin

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 ->  <br>    // Usar lista de direcciones<br>}
Kotlin

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()  
    }  
}
Kotlin
  • 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 un callback en una función suspend.
  • 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)
}
Kotlin

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
    }
}
Kotlin

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)  
        }  
    }}
Kotlin

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  
}
Kotlin

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()  
        }  
    }
}
Kotlin

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:

Apúntate ahora a la masterclass gratuita: Todo lo que necesitas saber para utilizar Jetpack Compose de forma efectiva desde hoy, y llévate un regalo muy especial: