En este post te mostraré cómo obtener la última ubicación del usuario usando el lenguaje de programación Kotlin en Android.
Te enseñaré a conocer la última ubicación conocida y a escuchar los cambios de ubicación para saber cuando la posición del usuario cambia.
De este modo podrías vigilar la ubicación del usuario conforme se mueve, y ya después de eso puedes hacer cualquier cosa con la ubicación, por ejemplo enviarla a un servidor, colocarla en un mapa, etcétera.
Agregando librería de los Google Play Services
Primero hay que agregar una librería. Vamos a build.gradle
y agregamos la última versión de los Google Play Services. Aquí vas a encontrar la última versión, en mi caso lo encontré así y entonces lo que agregamos al archivo es:
implementation 'com.google.android.gms:play-services-location:18.0.0'
Elegimos sync now y esperamos a que el Gradle se sincronice, descargue librerías, etcétera.
Mi gradle quedó así al final, solo como ejemplo:
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
compileSdk 31
defaultConfig {
applicationId "me.parzibyte.a"
minSdk 21
targetSdk 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
viewBinding true
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'com.google.android.gms:play-services-location:18.0.0'
}
Lo que importa es la línea 52.
Nota: recuerda que esta versión puede cambiar, y que siempre debes usar la última versión. Te dejé un link anteriormente para ir a al página oficial y conocer la versión actualizada.
Sobre los permisos
A partir de Android 6, debes obtener permisos en tiempo de ejecución además de indicarlo en el AndroidManifest. Yo acabo de hacer un post de cómo obtener los permisos de ubicación, te invito a leerlo.
Obtener ubicación en Android
Lo que vamos a hacer es crear una variable de tipo FusedLocationProviderClient
. Esta variable nos va a permitir acceder a dos cosas:
- Última ubicación conocida
- Ubicación en tiempo real del usuario cada que la misma cambie
Es importante acceder a la última ubicación conocida (con lastLocation) en el init de nuestra app, pues debemos tener algo para empezar, ya que no sabemos cuándo va a cambiar la ubicación del usuario, por ello es que al inicio usaremos la última conocida.
fusedLocationClient.lastLocation.addOnSuccessListener {
if (it != null) {
imprimirUbicacion(it)
} else {
Log.d(LOG_TAG, "No se pudo obtener la ubicación")
}
}
También vamos a necesitar un LocationCallback
pues vamos a suscribirnos a los cambios de ubicación, y cada que la ubicación cambie va a invocar a ese callback con la nueva ubicación del usuario.
Por otro lado, necesitamos un LocationRequest
para solicitar notificaciones del cambio de ubicación. En esta solicitud de ubicación indicamos cada cuánto queremos recibirlas, la prioridad y otras cosas que puedes personalizar.
val locationRequest = LocationRequest.create().apply {
interval = 10000
fastestInterval = 5000
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult?) {
locationResult ?: return
Log.d(LOG_TAG, "Se recibió una actualización")
for (location in locationResult.locations) {
imprimirUbicacion(location)
}
}
}
fusedLocationClient.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
Estamos solicitando las actualizaciones de ubicación en la línea 15, pero antes definimos el callback en la línea 7. En esa función vamos a recibir un arreglo de Location,
y lo que yo hago es imprimirla.
Aquí la función que imprime:
fun imprimirUbicacion(ubicacion: Location) {
Log.d(LOG_TAG, "Latitud es ${ubicacion.latitude} y la longitud es ${ubicacion.longitude}")
}
Nota: yo solo accedo a latitude
y longitude
pero puedes acceder a otras propiedades como la precisión o el proveedor.
Nota 2: es importante encerrar el acceso a la ubicación en un try catch esperando una excepción de tipo SecurityException
ya que puede que el usuario no haya dado permiso.
Poniendo todo junto
Ahora te mostraré el código completo de mi fragmento. Yo estoy solicitando los permisos de ubicación y cuando los obtengo es cuando invoco a la última ubicación, la imprimo y luego me suscribo a las actualizaciones de ubicación.
Si te sirve de algo, te lo dejo aquí. Recuerda tomarlo como guía, y no copies y pegues sin entender el código.
package me.parzibyte.a
import android.Manifest
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.content.pm.PackageManager
import android.location.Location
import android.os.Build
import android.os.Looper
import android.util.Log
import androidx.core.content.ContextCompat
import com.google.android.gms.location.*
class EnviarUbicacionService : Fragment() {
private lateinit var fusedLocationClient: FusedLocationProviderClient
/////
private lateinit var locationCallback: LocationCallback
private val CODIGO_PERMISOS_UBICACION_SEGUNDO_PLANO = 2106
private val LOG_TAG = "EnviarUbicacion"
private var haConcedidoPermisos = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
verificarPermisos()
}
fun imprimirUbicacion(ubicacion: Location) {
Log.d(LOG_TAG, "Latitud es ${ubicacion.latitude} y la longitud es ${ubicacion.longitude}")
}
fun onPermisosConcedidos() {
// Hasta aquí sabemos que los permisos ya están concedidos
fusedLocationClient = LocationServices.getFusedLocationProviderClient(activity)
try {
fusedLocationClient.lastLocation.addOnSuccessListener {
if (it != null) {
imprimirUbicacion(it)
} else {
Log.d(LOG_TAG, "No se pudo obtener la ubicación")
}
}
//////
val locationRequest = LocationRequest.create().apply {
interval = 10000
fastestInterval = 5000
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult?) {
locationResult ?: return
Log.d(LOG_TAG, "Se recibió una actualización")
for (location in locationResult.locations) {
imprimirUbicacion(location)
}
}
}
fusedLocationClient.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
} catch (e: SecurityException) {
Log.d(LOG_TAG, "Tal vez no solicitaste permiso antes")
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_enviar_ubicacion_service, container, false)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (requestCode == CODIGO_PERMISOS_UBICACION_SEGUNDO_PLANO) {
val todosLosPermisosConcedidos =
grantResults.all { it == PackageManager.PERMISSION_GRANTED }
if (grantResults.isNotEmpty() && todosLosPermisosConcedidos) {
haConcedidoPermisos = true;
onPermisosConcedidos()
Log.d(LOG_TAG, "El usuario concedió todos los permisos")
} else {
Log.d(LOG_TAG, "Uno o más permisos fueron denegados")
}
}
}
private fun verificarPermisos() {
val permisos = arrayListOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
)
// Segundo plano para Android Q
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
permisos.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
}
val permisosComoArray = permisos.toTypedArray()
if (tienePermisos(permisosComoArray)) {
haConcedidoPermisos = true
onPermisosConcedidos()
Log.d(LOG_TAG, "Los permisos ya fueron concedidos")
} else {
solicitarPermisos(permisosComoArray)
}
}
private fun solicitarPermisos(permisos: Array<String>) {
Log.d(LOG_TAG, "Solicitando permisos...")
requestPermissions(
permisos,
CODIGO_PERMISOS_UBICACION_SEGUNDO_PLANO
)
}
private fun tienePermisos(permisos: Array<String>): Boolean {
return permisos.all {
return ContextCompat.checkSelfPermission(
requireActivity(),
it
) == PackageManager.PERMISSION_GRANTED
}
}
}