Recibe actualizaciones de ubicación en Android con Kotlin

1. Antes de comenzar

Android 10 y 11 les brindan a los usuarios más control acceso a las ubicaciones de sus dispositivos.

Cuando una app que se ejecuta en Android 11 solicita acceso a la ubicación, los usuarios tienen cuatro opciones:

  • Permitir todo el tiempo
  • Permitir solo con la app en uso (en Android 10)
  • Solo una vez (en Android 11)
  • Rechazar

Android 10

6a1029175b467c77.png

Android 11

73d8cc88c5877c25.png

En este codelab, aprenderás a recibir actualizaciones de ubicación y a admitir la ubicación en cualquier versión de Android, especialmente Android 10 y 11. Al final del codelab, podrás contar con una app que siga las prácticas recomendadas actuales para recuperar actualizaciones de ubicación.

Requisitos previos

Actividades

  • Sigue las prácticas recomendadas para la ubicación en Android.
  • Controla los permisos de ubicación en primer plano (cuando el usuario solicita que la app acceda a la ubicación del dispositivo mientras la app está en uso).
  • Modifica una app existente para agregar compatibilidad con la solicitud de acceso a la ubicación. Para ello, agrega código para suscribirse a la ubicación y anular la suscripción.
  • Agrega compatibilidad a la app para Android 10 y 11 agregando lógica para acceder a la ubicación en primer plano o mientras está en uso.

Requisitos

  • Android Studio 3.4 o una versión posterior para ejecutar el código
  • Un dispositivo o emulador que ejecute una vista previa para desarrolladores de Android 10 y 11

2. Comenzar

Clona el repositorio del proyecto inicial

Para comenzar lo antes posible, puedes compilar este proyecto inicial. Si tienes Git instalado, simplemente puedes ejecutar el siguiente comando:

 git clone https://github.com/android/codelab-while-in-use-location

No dudes en visitar directamente la página de GitHub.

Si no tienes Git, puedes obtener el proyecto como un archivo ZIP:

Descargar ZIP

Importa el proyecto

Abre Android Studio y selecciona "Open an existing Android Studio project". desde la pantalla de bienvenida y abre el directorio del proyecto.

Después de que se cargue el proyecto, es posible que aparezca una alerta que indique que Git no está realizando un seguimiento de todos los cambios locales. Puedes hacer clic en Ignorar. (No enviarás ningún cambio al repositorio de Git).

En la esquina superior izquierda de la ventana del proyecto, deberías ver una imagen similar a la que se muestra a continuación si estás en la vista Android. (Si estás en la vista Project, debes expandir el proyecto para ver lo mismo).

fa825dae96c5dc18.png

Hay dos carpetas (base y complete). Cada uno se conoce como un "módulo".

Ten en cuenta que Android Studio puede tardar varios segundos la primera vez que compile el proyecto en segundo plano. Durante este tiempo, verás el siguiente mensaje en la barra de estado de la parte inferior de Android Studio:

c2273e7835c0841a.png

Espera hasta que Android Studio termine de indexar y compilar el proyecto antes de realizar cambios en el código. De esa manera, Android Studio podrá extraer todos los componentes necesarios.

Si aparece un mensaje que dice Volver a cargar para que se apliquen los cambios de idioma o algo similar, selecciona .

Comprende el proyecto inicial

Ya está todo configurado y listo para solicitar la ubicación en la app. Usa el módulo base como punto de partida. Durante cada paso, agrega código al módulo base. Cuando hayas terminado este codelab, el código del módulo base debería coincidir con el contenido del módulo complete. El módulo complete se puede usar para revisar tu trabajo o como referencia si tienes algún problema.

Los componentes clave incluyen los siguientes:

  • MainActivity: Es la IU que permite al usuario permitir que la app acceda a la ubicación del dispositivo.
  • LocationService: Es un servicio que suscribe o anula la suscripción a los cambios de ubicación, y que asciende a un servicio en primer plano (con una notificación) si el usuario sale de la actividad de la app. Agrega el código de ubicación aquí.
  • Util: Agrega funciones de extensión para la clase Location y guarda la ubicación en SharedPreferences (capa de datos simplificada).

Configuración del emulador

Si quieres obtener información para configurar un emulador de Android, consulta Cómo ejecutar un emulador.

Ejecuta el proyecto inicial

Ejecuta tu app.

  1. Conecta tu dispositivo Android a la computadora o inicia un emulador. (Asegúrate de que el dispositivo ejecute Android 10 o una versión posterior).
  2. En la barra de herramientas, selecciona la configuración base en el selector desplegable y haz clic en Run:

99600e9d44527ab.png

  1. Observa que aparece la siguiente app en tu dispositivo:

99bf1dae46f99af3.png

Es posible que notes que no aparece información de ubicación en la pantalla de resultados. Esto se debe a que aún no agregaste el código de ubicación.

3. Agregando ubicación

Conceptos

El objetivo de este codelab es mostrarte cómo recibir actualizaciones de ubicación y, eventualmente, admitir Android 10 y Android 11.

Sin embargo, antes de comenzar a programar, tiene sentido revisar los conceptos básicos.

Tipos de acceso a la ubicación

Quizás recuerdes las cuatro opciones diferentes para acceder a la ubicación desde el comienzo de este codelab. Veamos qué significan:

  • Permitir solo con la app en uso
  • Esta es la opción recomendada para la mayoría de las apps. También conocido como "durante el uso" o "solo en primer plano" , esta opción se agregó en Android 10 y permite que los desarrolladores recuperen la ubicación solo mientras la app se usa de forma activa. Se considera que una app está activa si se cumple alguna de las siguientes condiciones:
  • Una actividad es visible.
  • Se está ejecutando un servicio en primer plano con una notificación continua.
  • Solo una vez
  • que se agregó en Android 11 y es lo mismo que Permitir solo con la app en uso, pero por un tiempo limitado. Para obtener más información, consulta Permisos únicos.
  • Denegar
  • Esta opción impide el acceso a la información de ubicación.
  • Permitir todo el tiempo
  • Esta opción permite el acceso a la ubicación todo el tiempo, pero requiere un permiso adicional para Android 10 y versiones posteriores. También debes asegurarte de tener un caso de uso válido y de cumplir con las políticas de ubicación. No abordarás esta opción en este codelab, ya que es un caso de uso menos frecuente. Sin embargo, si tienes un caso de uso válido y quieres comprender cómo administrar correctamente la ubicación todo el tiempo, incluido el acceso a la ubicación en segundo plano, revisa la muestra LocationUpdatesBackgroundKotlin.

Servicios, servicios en primer plano y vinculación

Para admitir completamente las actualizaciones de ubicación de la opción Permitir solo con la app en uso, debes tener en cuenta el momento en que el usuario salga de la app. Si quieres seguir recibiendo actualizaciones en esa situación, debes crear un Service en primer plano y asociarlo con un Notification.

Además, si deseas usar el mismo Service para solicitar actualizaciones de ubicación cuando tu app está visible y el usuario sale de ella, debes vincular o desvincular ese Service al elemento de la IU.

Debido a que este codelab se enfoca solo en obtener actualizaciones de ubicación, puedes encontrar todo el código que necesitas en la clase ForegroundOnlyLocationService.kt. Puedes explorar esa clase y la MainActivity.kt para ver cómo funcionan en conjunto.

Para obtener más información, consulta Descripción general de los servicios y Descripción general de los servicios vinculados.

Permisos

Para recibir actualizaciones de ubicación de un objeto NETWORK_PROVIDER o GPS_PROVIDER, debes solicitar el permiso del usuario declarando el permiso ACCESS_COARSE_LOCATION o ACCESS_FINE_LOCATION, respectivamente, en el archivo de manifiesto de Android. Sin estos permisos, tu app no podrá solicitar acceso a la ubicación durante el tiempo de ejecución.

Esos permisos abarcan los casos de Solo una vez y Permitir solo con la app en uso cuando la app se usa en un dispositivo con Android 10 o versiones posteriores.

Ubicación

Tu app puede acceder al conjunto de servicios de ubicación compatibles a través de las clases del paquete com.google.android.gms.location.

Observa las clases principales:

  • FusedLocationProviderClient
  • Este es el componente central del marco de trabajo de ubicación. Una vez creada, la usarás para solicitar actualizaciones de ubicación y obtener la ubicación conocida más reciente.
  • LocationRequest
  • Este es un objeto de datos que contiene parámetros de calidad de servicio para las solicitudes (intervalos de actualizaciones, prioridades y precisión). Se pasa a FusedLocationProviderClient cuando solicitas actualizaciones de ubicación.
  • LocationCallback
  • Se usa para recibir notificaciones cuando la ubicación del dispositivo cambia o ya no se puede determinar. Se pasa un LocationResult donde puedes obtener el Location para guardar en tu base de datos.

Ahora que tienes una idea básica de lo que estás haciendo, comienza a usar el código.

4. Cómo agregar funciones de ubicación

Este codelab se enfoca en la opción de ubicación más común: Permitir solo con la app en uso.

Para recibir actualizaciones de ubicación, la app debe tener una actividad visible o un servicio ejecutándose en primer plano (con una notificación).

Permisos

El objetivo de este codelab es mostrar cómo recibir actualizaciones de ubicación, no cómo solicitar permisos de ubicación, por lo que el código basado en permisos ya está escrito por ti. Puedes omitir este paso si ya lo entiendes.

A continuación, se muestran los permisos destacados (no se requiere ninguna acción para esta parte):

  1. Declara qué permiso usas en AndroidManifest.xml.
  2. Antes de intentar acceder a la información de ubicación, verifica si el usuario le otorgó permiso a la app para hacerlo. Si tu app aún no recibió permiso, solicita acceso.
  3. Controla la elección de permisos del usuario. (Puedes ver este código en MainActivity.kt).

Si buscas TODO: Step 1.0, Review Permissions en AndroidManifest.xml o MainActivity.kt, verás todo el código escrito para los permisos.

Para obtener más información, consulta Descripción general de los permisos.

Ahora, empieza a escribir algún código de ubicación.

Revisa las variables clave necesarias para las actualizaciones de ubicación

En el módulo base, busca TODO: Step 1.1, Review variables en

Archivo ForegroundOnlyLocationService.kt.

No es necesario realizar ninguna acción en este paso. Solo debes revisar el siguiente bloque de código, junto con los comentarios, para comprender las clases y variables clave que usas para recibir actualizaciones de ubicación.

// TODO: Step 1.1, Review variables (no changes).
// FusedLocationProviderClient - Main class for receiving location updates.
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient

// LocationRequest - Requirements for the location updates, i.e., how often you
// should receive updates, the priority, etc.
private lateinit var locationRequest: LocationRequest

// LocationCallback - Called when FusedLocationProviderClient has a new Location.
private lateinit var locationCallback: LocationCallback

// Used only for local storage of the last known location. Usually, this would be saved to your
// database, but because this is a simplified sample without a full database, we only need the
// last location to create a Notification if the user navigates away from the app.
private var currentLocation: Location? = null

Revisa la inicialización de FusedLocationProviderClient

En el módulo base, busca TODO: Step 1.2, Review the FusedLocationProviderClient en el archivo ForegroundOnlyLocationService.kt. El código debería ser similar al siguiente:

// TODO: Step 1.2, Review the FusedLocationProviderClient.
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)

Como se mencionó en los comentarios anteriores, esta es la clase principal para obtener actualizaciones sobre la ubicación. La variable ya se inicializó, pero es importante que revises el código para comprender cómo se inicializó. Más adelante, agregarás código aquí para solicitar actualizaciones de ubicación.

Inicializa la LocationRequest

  1. En el módulo base, busca TODO: Step 1.3, Create a LocationRequest en el archivo ForegroundOnlyLocationService.kt.
  2. Agrega el siguiente código después del comentario.

El código de inicialización LocationRequest agrega la calidad adicional de los parámetros de servicio que necesitas para tu solicitud (intervalos, tiempo de espera máximo y prioridad).

// TODO: Step 1.3, Create a LocationRequest.
locationRequest = LocationRequest.create().apply {
   // Sets the desired interval for active location updates. This interval is inexact. You
   // may not receive updates at all if no location sources are available, or you may
   // receive them less frequently than requested. You may also receive updates more
   // frequently than requested if other applications are requesting location at a more
   // frequent interval.
   //
   // IMPORTANT NOTE: Apps running on Android 8.0 and higher devices (regardless of
   // targetSdkVersion) may receive updates less frequently than this interval when the app
   // is no longer in the foreground.
   interval = TimeUnit.SECONDS.toMillis(60)

   // Sets the fastest rate for active location updates. This interval is exact, and your
   // application will never receive updates more frequently than this value.
   fastestInterval = TimeUnit.SECONDS.toMillis(30)

   // Sets the maximum time when batched location updates are delivered. Updates may be
   // delivered sooner than this interval.
   maxWaitTime = TimeUnit.MINUTES.toMillis(2)

   priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
  1. Lee los comentarios para entender cómo funciona cada uno.

Inicializa la LocationCallback

  1. En el módulo base, busca TODO: Step 1.4, Initialize the LocationCallback en el archivo ForegroundOnlyLocationService.kt.
  2. Agrega el siguiente código después del comentario.
// TODO: Step 1.4, Initialize the LocationCallback.
locationCallback = object : LocationCallback() {
    override fun onLocationResult(locationResult: LocationResult) {
        super.onLocationResult(locationResult)

        // Normally, you want to save a new location to a database. We are simplifying
        // things a bit and just saving it as a local variable, as we only need it again
        // if a Notification is created (when the user navigates away from app).
        currentLocation = locationResult.lastLocation

        // Notify our Activity that a new location was added. Again, if this was a
        // production app, the Activity would be listening for changes to a database
        // with new locations, but we are simplifying things a bit to focus on just
        // learning the location side of things.
        val intent = Intent(ACTION_FOREGROUND_ONLY_LOCATION_BROADCAST)
        intent.putExtra(EXTRA_LOCATION, currentLocation)
        LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent)

        // Updates notification content if this service is running as a foreground
        // service.
        if (serviceRunningInForeground) {
            notificationManager.notify(
                NOTIFICATION_ID,
                generateNotification(currentLocation))
        }
    }
}

El objeto LocationCallback que creas aquí es la devolución de llamada que llamará el objeto FusedLocationProviderClient cuando haya una nueva actualización de ubicación disponible.

En tu devolución de llamada, primero obtienes la ubicación más reciente con un objeto LocationResult. Luego, debes notificar a tu Activity sobre la nueva ubicación mediante una transmisión local (si está activa) o actualizar la Notification si este servicio se ejecuta como Service en primer plano.

  1. Lee los comentarios para entender qué hace cada parte.

Suscribirse a los cambios de ubicación

Ahora que inicializaste todo, debes indicarle a FusedLocationProviderClient que deseas recibir actualizaciones.

  1. En el módulo base, busca Step 1.5, Subscribe to location changes en el archivo ForegroundOnlyLocationService.kt.
  2. Agrega el siguiente código después del comentario.
// TODO: Step 1.5, Subscribe to location changes.
fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())

La llamada a requestLocationUpdates() le indica al FusedLocationProviderClient que quieres recibir actualizaciones de ubicación.

Es probable que reconozcas LocationRequest y LocationCallback que definiste antes. Esos le permiten al FusedLocationProviderClient conocer los parámetros de calidad de servicio de tu solicitud y lo que debe llamar cuando tiene una actualización. Por último, el objeto Looper especifica el subproceso para la devolución de llamada.

También puedes observar que este código se encuentra dentro de una sentencia try/catch. Este método requiere el bloqueo debido a que una SecurityException se produce cuando tu app no tiene permiso para acceder a la información de ubicación.

Cómo anular la suscripción a los cambios de ubicación

Cuando la app ya no necesita acceder a la información de ubicación, es importante anular la suscripción a las actualizaciones de ubicación.

  1. En el módulo base, busca TODO: Step 1.6, Unsubscribe to location changes en el archivo ForegroundOnlyLocationService.kt.
  2. Agrega el siguiente código después del comentario.
// TODO: Step 1.6, Unsubscribe to location changes.
val removeTask = fusedLocationProviderClient.removeLocationUpdates(locationCallback)
removeTask.addOnCompleteListener { task ->
   if (task.isSuccessful) {
       Log.d(TAG, "Location Callback removed.")
       stopSelf()
   } else {
       Log.d(TAG, "Failed to remove Location Callback.")
   }
}

El método removeLocationUpdates() configura una tarea para que FusedLocationProviderClient sepa que ya no quieres recibir actualizaciones de ubicación de tu LocationCallback. addOnCompleteListener() proporciona la devolución de llamada para completar y ejecuta Task.

Al igual que en el paso anterior, quizás hayas notado que este código se encuentra dentro de una sentencia try/catch. Este método requiere el bloqueo debido a que una SecurityException se produce cuando tu app no tiene permiso para acceder a la información de ubicación.

Es posible que te preguntes cuándo se llama a los métodos que contienen el código de suscripción o anulación de suscripción. Se activan en la clase principal cuando el usuario presiona el botón. Si deseas verlo, consulta la clase MainActivity.kt.

Ejecutar app

Ejecuta la app desde Android Studio y prueba el botón de ubicación.

Deberías ver la información de ubicación en la pantalla de resultados. Esta es una app completamente funcional para Android 9.

2ae45c4e297e3681.png

d66089bfb532e993.png

5. Compatibilidad con Android 10

En esta sección, agregarás compatibilidad con Android 10.

Tu app ya se suscribió a los cambios de ubicación, por lo que no hay mucho trabajo por hacer.

De hecho, lo único que debes hacer es especificar que tu servicio en primer plano se use con fines de ubicación.

SDK de destino 29

  1. En el módulo base, busca TODO: Step 2.1, Target Android 10 and then Android 11. en el archivo build.gradle.
  2. Realiza estos cambios:
  3. Establece targetSdkVersion en 29.

El código debería ser similar al siguiente:

android {
   // TODO: Step 2.1, Target Android 10 and then Android 11.
   compileSdkVersion 29
   defaultConfig {
       applicationId "com.example.android.whileinuselocation"
       minSdkVersion 26
       targetSdkVersion 29
       versionCode 1
       versionName "1.0"
   }
...
}

Después de hacerlo, se te pedirá que sincronices tu proyecto. Haz clic en Sincronizar ahora.

153f70847e0ec320.png

Luego de esa fecha, tu app estará casi lista para Android 10.

Agrega un tipo de servicio en primer plano

En Android 10, debes incluir el tipo de tu servicio en primer plano si necesitas acceso a la ubicación mientras está en uso. En tu caso, se usa para obtener información de ubicación.

En el módulo base, busca TODO: 2.2, Add foreground service type en AndroidManifest.xml y agrega el siguiente código al elemento <service>:

android:foregroundServiceType="location"

El código debería ser similar al siguiente:

<application>
   ...

   <!-- Foreground services in Android 10+ require type. -->
   <!-- TODO: 2.2, Add foreground service type. -->
   <service
       android:name="com.example.android.whileinuselocation.ForegroundOnlyLocationService"
       android:enabled="true"
       android:exported="false"
       android:foregroundServiceType="location" />
</application>

Eso es todo. Tu app admite la ubicación de Android 10 "mientras está en uso" siguiendo las prácticas recomendadas para la ubicación en Android.

Ejecutar app

Ejecuta la app desde Android Studio y prueba el botón de ubicación.

Todo debería funcionar como antes, pero ahora funciona en Android 10. Si no aceptaste los permisos para las ubicaciones, ahora deberías ver la pantalla de permisos.

6a1029175b467c77.png

c7c1d226e49a121.png

39a262b66a275f66.png

6. Compatibilidad con Android 11

En esta sección, el objetivo es Android 11.

¡Buenas noticias! No necesitas realizar cambios en ningún archivo, excepto en el archivo build.gradle.

SDK de destino 11

  1. En el módulo base, busca TODO: Step 2.1, Target SDK en el archivo build.gradle.
  2. Realiza estos cambios:
  3. De compileSdkVersion a 30
  4. De targetSdkVersion a 30

El código debería ser similar al siguiente:

android {
   TODO: Step 2.1, Target Android 10 and then Android 11.
   compileSdkVersion 30
   defaultConfig {
       applicationId "com.example.android.whileinuselocation"
       minSdkVersion 26
       targetSdkVersion 30
       versionCode 1
       versionName "1.0"
   }
...
}

Después de hacerlo, se te pedirá que sincronices tu proyecto. Haz clic en Sincronizar ahora.

153f70847e0ec320.png

Luego, tu app estará lista para Android 11.

Ejecutar app

Ejecuta la app desde Android Studio y, luego, intenta hacer clic en el botón.

Todo debería funcionar como antes, pero ahora funciona en Android 11. Si no aceptaste los permisos para las ubicaciones, ahora deberías ver la pantalla de permisos.

73d8cc88c5877c25.png

cc98fac6e089bc4.png

7. Estrategias de ubicación para Android

Si verificas y solicitas permisos de ubicación como se muestra en este codelab, tu app podrá hacer un seguimiento correcto de su nivel de acceso con respecto a la ubicación del dispositivo.

En esta página, se incluyen algunas prácticas recomendadas clave relacionadas con los permisos de ubicación. Para obtener más información sobre cómo mantener datos seguros, consulta Prácticas recomendadas de permisos de apps.

Solicita solo los permisos que necesitas

Solicita permisos solo cuando los necesites. Por ejemplo:

  • No solicites un permiso de ubicación cuando se inicia la app, a menos que sea absolutamente necesario.
  • Si tu app está orientada a Android 10 o versiones posteriores y tienes un servicio en primer plano, declara un foregroundServiceType de "location" en el manifiesto.
  • No solicites permisos de ubicación en segundo plano, a menos que tengas un caso de uso válido, como se describe en Acceso más seguro y transparente a la ubicación del usuario.

Admite la degradación elegante si no se otorga permiso

Para mantener una buena experiencia del usuario, diseña tu app de modo que pueda manejar correctamente las siguientes situaciones:

  • Tu app no tiene acceso a la información sobre la ubicación.
  • Tu app no tiene acceso a la información de ubicación cuando se ejecuta en segundo plano.

8. Felicitaciones

Aprendiste a recibir actualizaciones de ubicación en Android teniendo en cuenta las prácticas recomendadas.

Más información