Receber atualizações do local no Android com o Kotlin

O Android 10 e 11 oferece aos usuários mais controle sobre os apps e o acesso à localização do dispositivo.

Quando um app em execução no Android 11 solicita acesso à localização, os usuários têm quatro opções:

  • Permitir o tempo todo
  • Permitir durante o uso do app (no Android 10)
  • Somente uma vez (no Android 11)
  • Negar

Android 10

Android 11

Neste codelab, você aprenderá a receber atualizações de localização e oferecer compatibilidade com a localização em qualquer versão do Android, especialmente Android 10 e 11. Ao final do codelab, você terá um app que segue as práticas recomendadas atuais para recuperar atualizações de localização.

Pré-requisitos

O que você aprenderá

  • Siga as práticas recomendadas para localização no Android.
  • Lidar com permissões de localização em primeiro plano (quando o usuário solicita que seu app acesse a localização do dispositivo enquanto está em uso)
  • Para modificar um app existente e adicionar suporte para solicitar acesso ao local, adicione um código para assinar e cancelar a inscrição no local.
  • Adicione compatibilidade com o app para o Android 10 e 11 adicionando lógica para acessar a localização na localização em primeiro plano ou durante o uso.

O que é necessário

  • Android Studio 3.4 ou mais recente para executar o código
  • Um dispositivo/emulador com uma Visualização do desenvolvedor do Android 10 e 11

Clonar o repositório inicial do projeto

Para começar o mais rápido possível, use este projeto inicial. Se você tiver o Git instalado, basta executar o seguinte comando:

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

Fique à vontade para acessar a página do GitHub diretamente.

Se você não tiver o Git, poderá acessar o projeto como um arquivo ZIP:

Fazer o download do ZIP

Importar o projeto

Abra o Android Studio, selecione "Open an existing Android Studio project" na tela de boas-vindas e abra o diretório do projeto.

Após o carregamento do projeto, você verá um alerta informando que o Git não está rastreando todas as mudanças locais. Clique em Ignorar. Você não enviará mudanças ao repositório Git.

No canto superior esquerdo da janela do projeto, você verá algo semelhante à imagem abaixo se estiver na visualização Android. Se você estiver na visualização Project, precisará expandi-la para ver a mesma coisa.

Há duas pastas (base e complete). Cada uma é conhecida como um "módulo".

O Android Studio pode levar vários segundos para compilar o projeto em segundo plano pela primeira vez. Durante esse período, você verá a seguinte mensagem na barra de status na parte inferior do Android Studio:

Aguarde até que o Android Studio termine de indexar e criar o projeto antes de fazer mudanças no código. Isso permitirá que o Android Studio extraia todos os componentes necessários.

Se você receber a mensagem Reload for language changes to take take? ou algo semelhante, selecione Yes.

Entender o projeto inicial

Você está pronto e pronto para solicitar a localização no app. Use o módulo base como ponto de partida. Em cada etapa, adicione código ao módulo base. No fim deste codelab, o código no módulo base será igual ao conteúdo do módulo complete. O módulo complete pode ser usado para verificar seu trabalho ou para consulta em caso de problemas.

Os componentes principais incluem:

  • MainActivity: IU para o usuário permitir que o app acesse a localização do dispositivo
  • LocationService: serviço que se inscreve e cancela a inscrição para alterações de local e se promove como um serviço em primeiro plano (com uma notificação) quando o usuário sai da atividade do app. Adicione o código do local aqui.
  • Util: adiciona funções de extensão para a classe Location e salva o local em SharedPreferences (camada de dados simplificada).

Configuração do emulador

Para saber mais sobre como configurar um Android Emulator, consulte Executar em um emulador.

Executar o projeto inicial

Execute o app.

  1. Conecte seu dispositivo Android ao computador ou inicie um emulador. Confira se o dispositivo está executando o Android 10 ou mais recente.
  2. Na barra de ferramentas, selecione a configuração base no seletor suspenso e clique em Executar:


  1. O seguinte app aparece no seu dispositivo:


Observe que nenhuma informação de localização aparecerá na tela de saída. Isso ocorre porque você ainda não adicionou o código de local.

conceitos

O foco deste codelab é mostrar como receber atualizações de localização para oferecer compatibilidade com o Android 10 e o Android 11.

No entanto, antes de começar a programar, vale a pena revisar os princípios básicos.

Tipos de acesso à localização

Você pode se lembrar das quatro opções diferentes de acesso à localização desde o início do codelab. Veja o que eles significam:

  • Permitir durante o uso do app
  • Essa é a opção recomendada para a maioria dos apps. Também conhecida como acesso "durante o uso" ou "somente em primeiro plano", essa opção foi adicionada no Android 10 e permite que os desenvolvedores recuperem a localização apenas enquanto o app está sendo ativamente usado. Um app será considerado ativo se uma das seguintes condições for verdadeira:
  • Uma atividade está visível.
  • Um serviço em primeiro plano está em execução com uma notificação em andamento.
  • Apenas uma vez
  • Adicionado no Android 11, isso é o mesmo que Permitir apenas durante o uso do app, mas por um período limitado. Para mais informações, consulte Permissões únicas.
  • Negar
  • Essa opção impede o acesso a informações de localização.
  • Permitir o tempo todo
  • Essa opção permite acesso à localização o tempo todo, mas requer uma permissão extra para o Android 10 e versões mais recentes. Também é preciso ter um caso de uso válido e obedecer às políticas de local. Você não abordará essa opção neste codelab, já que esse é um caso de uso mais raro. No entanto, se você tiver um caso de uso válido e quiser entender como processar corretamente a localização o tempo todo, incluindo o acesso à localização em segundo plano, consulte a amostra LocationUpdatesBackgroundKotlin.

Serviços, serviços em primeiro plano e vinculação

Para oferecer compatibilidade total com as atualizações de localização Permitir apenas durante o uso do app, é necessário considerar quando o usuário sai do app. Se você quiser continuar recebendo atualizações nessa situação, precisará criar um Service em primeiro plano e associá-lo a um Notification.

Além disso, se você quiser usar o mesmo Service para solicitar atualizações de localização quando o app estiver visível e quando o usuário sair do app, será necessário vincular/desvincular o Service ao elemento da IU.

Como este codelab se concentra apenas em receber atualizações de localização, você pode encontrar todo o código necessário na classe ForegroundOnlyLocationService.kt. Você pode navegar por essa classe e pelo MainActivity.kt para ver como elas funcionam juntas.

Veja mais informações em Visão geral dos serviços e Visão geral dos serviços vinculados.

Permissões

Para receber atualizações de localização de um NETWORK_PROVIDER ou GPS_PROVIDER, é necessário solicitar a permissão do usuário declarando a permissão ACCESS_COARSE_LOCATION ou ACCESS_FINE_LOCATION, respectivamente, no seu arquivo de manifesto do Android. Sem essas permissões, o app não poderá solicitar acesso à localização no momento da execução.

Essas permissões abrangem os casos Somente uma vez e Permitir durante o uso do app quando ele é usado em dispositivos com o Android 10 ou versões mais recentes.

Local

O app pode acessar o conjunto de serviços de localização compatíveis por meio de classes no pacote com.google.android.gms.location.

Veja as principais classes:

  • FusedLocationProviderClient
  • Este é o componente central do framework de localização. Depois de criado, você pode usá-lo para solicitar atualizações e conseguir o último local conhecido.
  • LocationRequest
  • Esse é um objeto de dados que contém parâmetros de qualidade de serviço para solicitações (intervalos de atualizações, prioridades e precisão). Ela é transmitida para o FusedLocationProviderClient quando você solicita atualizações de localização.
  • LocationCallback
  • Usado para receber notificações quando a localização do dispositivo mudar ou não for mais determinada. Ela recebe um LocationResult, onde é possível salvar Location no banco de dados.

Agora que você tem uma ideia básica do que está fazendo, comece a usar o código.

Este codelab se concentra na opção de local mais comum: Permitir durante o uso do app.

Para receber atualizações de localização, o app precisa ter uma atividade visível ou um serviço em execução em primeiro plano (com uma notificação).

Permissões

O objetivo deste codelab é mostrar como receber atualizações de localização, e não como solicitar permissões de localização. Portanto, o código com base em permissão já está programado. Se você já entende, é só pular.

Veja a seguir os destaques das permissões (nenhuma ação é necessária):

  1. Declare qual permissão você usa no AndroidManifest.xml.
  2. Antes de tentar acessar as informações de localização, verifique se o usuário concedeu permissão para que seu app faça isso. Se o app ainda não tiver recebido permissão, solicite acesso.
  3. Processe a escolha de permissão do usuário. É possível ver esse código no MainActivity.kt.

Se você pesquisar TODO: Step 1.0, Review Permissions na AndroidManifest.xml ou na MainActivity.kt, verá todo o código escrito para permissões.

Para saber mais, consulte Visão geral de permissões.

Agora, comece a escrever um código de localização.

Analise as principais variáveis necessárias para atualizações de localização

No módulo base, pesquise TODO: Step 1.1, Review variables na

arquivo ForegroundOnlyLocationService.kt.

Nenhuma ação é necessária nesta etapa. Você só precisa analisar o bloco de código a seguir, junto com os comentários, para entender as principais classes e variáveis que você usa para receber atualizações de localização.

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

Analisar a inicialização de FusedLocationProviderClient

No módulo base, pesquise TODO: Step 1.2, Review the FusedLocationProviderClient no arquivo ForegroundOnlyLocationService.kt. O código vai ficar assim:

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

Como mencionado nos comentários anteriores, esta é a principal classe para receber atualizações de localização. A variável já foi inicializada, mas é importante analisar o código para entender como ela foi inicializada. Adicione código aqui para solicitar atualizações de localização.

Inicialize o LocationRequest

  1. No módulo base, pesquise TODO: Step 1.3, Create a LocationRequest no arquivo ForegroundOnlyLocationService.kt.
  2. Adicione o seguinte código após o comentário.

O código de inicialização LocationRequest adiciona a qualidade extra dos parâmetros de serviço que você precisa para sua solicitação (intervalos, tempo de espera máximo e prioridade).

// 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. Leia os comentários para entender como cada um funciona.

Inicialize o LocationCallback

  1. No módulo base, pesquise TODO: Step 1.4, Initialize the LocationCallback no arquivo ForegroundOnlyLocationService.kt.
  2. Adicione o seguinte código após o comentário.
// 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.")
       }
   }
}

O LocationCallback que você criar aqui é o callback que o FusedLocationProviderClient chamará quando uma nova atualização de local estiver disponível.

No retorno de chamada, primeiro acesse o local mais recente usando um objeto LocationResult. Depois disso, você notifica a Activity sobre o novo local usando uma transmissão local (se ela estiver ativa) ou atualiza a Notification caso esse serviço seja executado como um Service em primeiro plano.

  1. Leia os comentários para entender o que cada parte faz.

Inscrever-se para receber alterações de local

Agora que você inicializou tudo, é necessário informar ao FusedLocationProviderClient que você quer receber atualizações.

  1. No módulo base, pesquise Step 1.5, Subscribe to location changes no arquivo ForegroundOnlyLocationService.kt.
  2. Adicione o seguinte código após o comentário.
// TODO: Step 1.5, Subscribe to location changes.
fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper())

A chamada requestLocationUpdates() informa ao FusedLocationProviderClient que você quer receber atualizações de localização.

Você provavelmente reconhece os LocationRequest e os LocationCallback que definiu anteriormente. Esse parâmetro informa ao FusedLocationProviderClient os parâmetros de qualidade do serviço para sua solicitação e o que ele precisa chamar quando tiver uma atualização. Por fim, o objeto Looper especifica a linha de execução para o callback.

Você também pode notar que esse código está em uma instrução try/catch. Esse método exige esse bloco porque ocorre uma SecurityException quando o app não tem permissão para acessar informações de localização.

Cancelar inscrição nas alterações de local

Quando o app não precisar mais de informações de localização, é importante cancelar a inscrição das atualizações de localização.

  1. No módulo base, pesquise TODO: Step 1.6, Unsubscribe to location changes no arquivo ForegroundOnlyLocationService.kt.
  2. Adicione o seguinte código após o comentário.
// 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.")
   }
}

O método removeLocationUpdates() configura uma tarefa para informar ao FusedLocationProviderClient que você não quer mais receber atualizações de localização para o LocationCallback. O addOnCompleteListener() fornece o callback para conclusão e executa o Task.

Como na etapa anterior, você deve ter percebido que esse código está em uma instrução try/catch. Esse método exige esse bloco porque ocorre uma SecurityException quando o app não tem permissão para acessar as informações de localização.

Você pode se perguntar quando os métodos que contêm o código de inscrição/cancelamento são chamados. Eles são acionados na classe principal quando o usuário toca no botão. Se quiser, veja a classe MainActivity.kt.

Execute o app.

Execute o app no Android Studio e teste o botão de localização.

Você verá as informações de localização na tela de saída. Este é um app totalmente funcional para o Android 9.

Nesta seção, você vai adicionar compatibilidade com o Android 10.

Como seu app já é assinante de mudanças de localização, não há muito trabalho a fazer.

Na verdade, tudo o que você precisa fazer é especificar que seu serviço em primeiro plano é usado para fins de localização.

SDK de destino 29

  1. No módulo base, pesquise TODO: Step 2.1, Target SDK 10 no arquivo build.gradle.
  2. Faça estas mudanças:
  1. Defina compileSdkVersion como 29.
  2. Defina buildToolsVersion como "29.0.3".
  3. Defina targetSdkVersion como 29.

O código vai ficar assim:

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

Em seguida, você precisará sincronizar o projeto. Clique em Sync Now.

Depois disso, o app estará quase pronto para o Android 10.

Adicionar tipo de serviço em primeiro plano

No Android 10, você precisará incluir o tipo do serviço em primeiro plano se precisar de acesso à localização durante o uso. No seu caso, ela está sendo usada para coletar informações de local.

No módulo base, pesquise a TODO: 2.2, Add foreground service type no AndroidManifest.xml e adicione o seguinte código ao elemento <service>:

android:foregroundServiceType="location"

O código vai ficar assim:

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

Pronto! Seu app é compatível com a localização do Android 10 enquanto ele está em uso, seguindo as práticas recomendadas de localização no Android.

Execute o app.

Execute o app no Android Studio e teste o botão de localização.

Tudo deveria funcionar como antes, mas agora funciona no Android 10. Se você não tinha aceitado as permissões dos locais antes, a tela de permissões será exibida.

Nesta seção, o app será direcionado ao Android 11.

Uma boa notícia é que você não precisa fazer alterações em nenhum arquivo, exceto no arquivo build.gradle.

SDK de destino R

  1. No módulo base, pesquise TODO: Step 2.1, Target SDK no arquivo build.gradle.
  2. Faça estas mudanças:
  1. compileSdkVersion a "android-R"
  2. targetSdkVersion a "R"

O código vai ficar assim:

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

Em seguida, você precisará sincronizar o projeto. Clique em Sync Now.

Depois disso, o app estará pronto para o Android 11.

Execute o app.

Execute o app no Android Studio e tente clicar no botão.

Tudo deveria funcionar como antes, mas agora funciona no Android 11. Se você não tinha aceitado as permissões dos locais antes, a tela de permissões será exibida.

Ao verificar e solicitar permissões de localização das formas mostradas neste codelab, seu app pode acompanhar o nível de acesso dele à localização do dispositivo.

Esta página lista algumas práticas recomendadas relacionadas às permissões de localização. Para mais informações sobre como manter os dados dos seus usuários seguros, consulte Práticas recomendadas de permissões do app.

Peça apenas as permissões necessárias

Solicite permissões somente quando necessário. Exemplo:

  • Não solicite uma permissão de localização na inicialização do aplicativo, a menos que seja absolutamente necessário.
  • Se o app for destinado ao Android 10 ou versões mais recentes e você tiver um serviço em primeiro plano, declare um foregroundServiceType de "location" no manifesto.
  • Não solicite permissões de localização em segundo plano, a menos que você tenha um caso de uso válido, conforme descrito em Acesso mais seguro e transparente à localização do usuário.

Apoiar a degradação suave se a permissão não for concedida

Para manter uma boa experiência do usuário, projete seu app para que ele possa lidar adequadamente com as seguintes situações:

  • Seu app não tem acesso a informações de localização.
  • Seu app não tem acesso às informações de localização quando executado em segundo plano.

Você aprendeu a receber atualizações de localização no Android seguindo as práticas recomendadas em mente.

Saiba mais