Android

Obtener ubicación en tiempo real con Android y Kotlin

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

}

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Entradas recientes

Creador de credenciales web – Aplicación gratuita

Hoy te voy a presentar un creador de credenciales que acabo de programar y que…

1 semana hace

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

2 semanas hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

2 semanas hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

2 semanas hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

2 semanas hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

2 semanas hace

Esta web usa cookies.