Recibe actualizaciones de ubicación en Android con Kotlin

Con Android 10 y 11, los usuarios tienen más control sobre sus apps y acceso a la ubicación 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 Android 10
  • Solo una vez (en Android 11)
  • Denegar

Android 10

Android 11

En este codelab, aprenderás cómo recibir actualizaciones de ubicación y admitir la ubicación en cualquier versión de Android, especialmente en Android 10 y 11. Al final del codelab, puedes tener 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.
  • Administrar permisos de ubicación en primer plano (cuando el usuario solicita que tu app acceda a la ubicación del dispositivo mientras está en uso)
  • Modifica una app existente a fin de agregar compatibilidad con la solicitud de acceso a la ubicación. Para ello, agrega un código que te permita suscribirte y anular la suscripción a la ubicación.
  • Agrega compatibilidad con la app para Android 10 y Android 11. Para ello, agrega 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 ejecuta una vista previa de Android 10 y 11 para desarrolladores

Clona el repositorio del proyecto inicial

Para comenzar lo más rápido posible, puedes compilar en este proyecto inicial. Si tienes Git instalado, simplemente puedes ejecutar el siguiente comando:

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

Puedes visitar la página de GitHub directamente.

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

Descargar ZIP

Importa el proyecto

Abre Android Studio, selecciona Open an existing Android Studio project en 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 más abajo si estás en la vista Android. (Si estás en la vista Project, debes expandir el proyecto para ver lo mismo).

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

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

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 recibes el mensaje Reload for language changes to take effect? o uno similar, selecciona .

Comprende el proyecto inicial

Ya puedes configurar la ubicación en la app y usar el módulo base como punto de partida. Durante cada paso, agrega código al módulo base. Para el momento en que hayas terminado con 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 verificar tu trabajo o para usarlo como referencia si tienes algún problema.

Entre los componentes clave, se incluyen los siguientes:

  • MainActivity: IU para que el usuario permita que la app acceda a la ubicación del dispositivo
  • LocationService: Es un servicio que se suscribe y anula la suscripción a los cambios de ubicación, y se promociona a sí mismo a un servicio en primer plano (con una notificación) si el usuario sale de la actividad de la app. Agrega 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

Para obtener información sobre cómo configurar un Android Emulator, consulta Cómo ejecutar en un emulador.

Cómo ejecutar 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:


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


Es posible que no veas información de ubicación en la pantalla de resultados. Esto se debe a que todavía no agregaste el código de ubicación.

Conceptos

Este codelab se enfoca en mostrarte cómo recibir actualizaciones de ubicación y, con el tiempo, admitir Android 10 y Android 11.

Sin embargo, antes de que comiences a programar, te recomendamos que revises los conceptos básicos.

Tipos de acceso a la ubicación

Quizás recuerdes las cuatro opciones diferentes de acceso a la ubicación desde el principio del codelab. Conoce su significado:

  • Permitir solo con la app en uso
  • Esta es la opción recomendada para la mayoría de las apps. Esta opción, también conocida como acceso "solo en uso" o "solo en primer plano", se agregó en Android 10 y les permite a los desarrolladores recuperar la ubicación solo mientras se usa la app 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 constante.
  • Solo una vez
  • En Android 11, se agregó lo mismo que en Permitir solo con la app en uso, pero por tiempo limitado. Para obtener más información, consulta Permisos únicos.
  • Denegar
  • Esta opción impide el acceso a la información de la 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 satisfacer las políticas de ubicación. En este codelab, no abordarás esta opción, ya que es un caso de uso poco 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 el ejemplo de LocationUpdatesBackgroundKotlin.

Servicios, servicios en primer plano y vinculación

Para admitir todas las actualizaciones de ubicación de Permitir solo cuando se usa la app, debes tener en cuenta cuándo el usuario sale de la app. Si deseas continuar recibiendo actualizaciones en esa situación, debes crear un elemento Service en primer plano y asociarlo con un elemento Notification.

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

Dado 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 el MainActivity.kt para ver cómo funcionan juntos.

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 NETWORK_PROVIDER o un GPS_PROVIDER, debes solicitar el permiso del usuario declarando el permiso ACCESS_COARSE_LOCATION o ACCESS_FINE_LOCATION, respectivamente, en tu archivo de manifiesto de Android. Sin estos permisos, la app no podrá solicitar acceso a la ubicación durante el tiempo de ejecución.

Esos permisos cubren los casos 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 admitidos por medio de las clases del paquete com.google.android.gms.location.

Observa las clases principales:

  • FusedLocationProviderClient
  • Este es el componente central del framework de ubicación. Una vez creada, la utilizas para solicitar actualizaciones de ubicación y obtener la última ubicación conocida.
  • LocationRequest
  • Este es un objeto de datos que contiene parámetros de calidad de servicio para las solicitudes (intervalos por actualizaciones, prioridades y exactitud). Esto se pasa a FusedLocationProviderClient cuando solicitas actualizaciones de ubicación.
  • LocationCallback
  • Se utiliza para recibir notificaciones cuando cambia la ubicación del dispositivo 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.

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

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

Permisos

El propósito 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. Si ya lo entendió, puede omitirlo.

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

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

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

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

Ahora, comienza a escribir 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 la

Archivo ForegroundOnlyLocationService.kt.

En este paso, no se requiere ninguna acción. Solo debes revisar el siguiente bloque de código, junto con los comentarios, para entender las clases y variables clave que usas a fin de 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 de ubicación. La variable ya está inicializada, pero es importante revisar el código para comprender cómo se inicializa. Agrega código en el futuro 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().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 comprender 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)

       if (locationResult?.lastLocation != null) {

           // 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 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))
           }
       } else {
           Log.d(TAG, "Location information isn't available.")
       }
   }
}

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

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

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

Suscribirse a los cambios de ubicación

Ahora que inicializaste todo, debes informarle a FusedLocationProviderClient que quieres 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.myLooper())

La llamada a requestLocationUpdates() permite que FusedLocationProviderClient sepa que quieres recibir actualizaciones de ubicación.

Es probable que reconozcas los valores de LocationRequest y LocationCallback que definiste antes. De esta manera, los FusedLocationProviderClient conocen los parámetros de calidad de servicio para tu solicitud y a qué deben llamar cuando se actualiza. Por último, el objeto Looper especifica el subproceso de la devolución de llamada.

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

Anular la suscripción a los cambios de ubicación

Cuando la app ya no necesita acceder a los datos 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 informarle a FusedLocationProviderClient que ya no deseas recibir actualizaciones de ubicación para tu LocationCallback. addOnCompleteListener() proporciona la devolución de llamada para completarse y ejecuta Task.

Al igual que en el paso anterior, es posible que hayas notado que este código se encuentra dentro de una sentencia try/catch. Este método requiere ese bloqueo porque se produce una SecurityException 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 la 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 tu 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 salida. Esta es una app completamente funcional para Android 9.

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

Dado que tu app ya se suscribió a los cambios de ubicación, no hay mucho que hacer.

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

SDK 29 de destino

  1. En el módulo base, busca TODO: Step 2.1, Target SDK 10 en el archivo build.gradle.
  2. Realiza estos cambios:
  1. Establece compileSdkVersion en 29.
  2. Establece buildToolsVersion en "29.0.3".
  3. Establece targetSdkVersion en 29.

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

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

Después de hacer esto, se te solicitará que sincronices tu proyecto. Haz clic en Sincronizar ahora.

Luego, la app estará casi lista para Android 10.

Cómo agregar un tipo de servicio en primer plano

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

En el módulo base, busca el elemento TODO: 2.2, Add foreground service type en el elemento 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>

Listo. Si tu app admite la ubicación de Android 10 para usar durante el uso, sigue las prácticas recomendadas de ubicación en Android.

Ejecutar app

Ejecuta tu 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 de las ubicaciones anteriormente, ahora deberías ver la pantalla de permisos.

En esta sección, te orientarás a Android 11.

¡Excelentes noticias! No necesitas modificar ningún archivo, excepto el build.gradle.

SDK de destino R

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

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

android {
   // TODO: Step 2.1, Target Android 10.
   compileSdkVersion "android-R"
   buildToolsVersion "29.0.2"
   defaultConfig {
       applicationId "com.example.android.whileinuselocation"
       minSdkVersion 26
       targetSdkVersion "R"
       versionCode 1
       versionName "1.0"
   }
...
}

Después de hacer esto, se te solicitará que sincronices tu proyecto. Haz clic en Sincronizar ahora.

Luego, estará lista para Android 11.

Ejecutar app

Ejecuta tu app desde Android Studio e intenta hacer clic en el botón.

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

Si verificas y solicitas permisos de ubicación de las maneras que se muestran en este codelab, la app puede llevar un registro 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 proteger tus datos de los usuarios, consulta las Prácticas recomendadas sobre permisos de apps.

Solicita solo los permisos que necesites

Solicita permisos solo cuando los necesites. Por ejemplo:

  • No solicites un permiso de ubicación cuando inicies 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 elemento 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.

Admitir degradación elegante si no se otorga permiso

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

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

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

Más información