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

1. Antes de começar

O Android 10 e 11 oferecem aos usuários mais controle sobre os apps acesso à localização dos dispositivos.

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)
  • Apenas uma vez (no Android 11)
  • Negar

Android 10

6a1029175b467c77.png

Android 11

73d8cc88c5877c25.png

Neste codelab, você vai aprender a receber atualizações de localização e oferecer suporte à localização em qualquer versão do Android, principalmente no Android 10 e 11. No final do codelab, você terá um app que siga as práticas recomendadas atuais para recuperar atualizações de localização.

Pré-requisitos

O que você aprenderá

  • Siga as práticas recomendadas de localização no Android.
  • Gerencie as permissões de localização em primeiro plano quando o usuário solicita que o app acesse a localização do dispositivo enquanto está em uso.
  • Modifique um app existente para permitir a solicitação de acesso ao local, adicionando código para inscrição e cancelamento da inscrição no local.
  • Adicione suporte ao app para Android 10 e 11 adicionando lógica para acessar a 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 executando uma prévia para desenvolvedores do Android 10 e 11.

2. Primeiros passos

Clonar o repositório inicial do projeto

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

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

Acesse diretamente a página do GitHub (link em inglês).

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

Importar o projeto

Abra o Android Studio e selecione Open an existing Android Studio project. na tela inicial 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 parecido com a imagem abaixo se estiver na visualização Android. Se você estiver na visualização Project, precisará expandi-lo para ver a mesma coisa.

fa825dae96c5dc18.png

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

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

c2273e7835c0841a.png

Aguarde o Android Studio terminar 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 Atualizar para que as alterações de idioma entrem em vigor? ou algo semelhante, selecione Sim.

Entender o projeto inicial

Está tudo configurado e pronto para solicitar a localização no app. Use o módulo base como ponto de partida. Durante cada etapa, adicione código ao módulo base. Quando você concluir este codelab, o código no módulo base vai corresponder 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 principais componentes incluem:

  • MainActivity: interface 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 em mudanças de localização e se promove a um serviço em primeiro plano (com uma notificação) se o usuário sai da atividade do app. Você adiciona o código do local aqui.
  • Util: adiciona funções de extensão para a classe Location e salva a localização em SharedPreferences (camada de dados simplificada).

Configuração do emulador

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

Executar o projeto inicial

Execute o app.

  1. Conecte o 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 Run:

99600e9d44527ab.png

  1. Observe que o seguinte app aparece no seu dispositivo:

99bf1dae46f99af3.png

Você pode notar que nenhuma informação de localização aparece na tela de saída. Isso ocorre porque você ainda não adicionou o código de local.

3. Adicionando local

Conceitos

O foco deste codelab é mostrar como receber atualizações de localização e, eventualmente, oferecer suporte ao Android 10 e ao Android 11.

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

Tipos de acesso à localização

Talvez você se lembre das quatro opções diferentes de acesso à localização desde o início do codelab. Confira o que eles significam:

  • Permitir durante o uso do app
  • Essa é a opção recomendada para a maioria dos apps. Também conhecido como "durante o uso" ou "apenas em primeiro plano" acesso, essa opção foi adicionada no Android 10 e permite que os desenvolvedores recuperem a localização apenas enquanto o app estiver sendo usado ativamente. Um app é considerado ativo se uma das seguintes condições é verdadeira:
  • Uma atividade está visível.
  • Um serviço em primeiro plano está sendo executado com uma notificação em andamento.
  • Apenas uma vez
  • Adicionado no Android 11, é o mesmo que Permitir 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 às informações de local.
  • Permitir o tempo todo
  • Essa opção permite acesso à localização o tempo todo, mas exige uma permissão extra para o Android 10 e versões mais recentes. Verifique também se você tem um caso de uso válido e se está em conformidade com as políticas de localização. Essa opção não será abordada neste codelab porque ela é um caso de uso mais raro. No entanto, se você tiver um caso de uso válido e quiser entender como lidar corretamente com a localização o tempo todo, incluindo o acesso à localização em segundo plano, consulte o exemplo LocationUpdatesBackgroundKotlin.

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

Para oferecer suporte total às atualizações de localização da opção Permitir durante o uso do app, você precisa considerar quando o usuário sai do app. Se você quiser continuar recebendo atualizações nessa situação, crie um Service em primeiro plano e associe-o 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 dele, será necessário vincular/desvincular esse Service ao elemento da interface.

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 eles funcionam juntos.

Para mais informações, consulte 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, solicite a permissão do usuário declarando a permissão ACCESS_COARSE_LOCATION ou ACCESS_FINE_LOCATION, respectivamente, no arquivo de manifesto do Android. Sem essas permissões, o app não poderá solicitar acesso à localização durante a execução.

Essas permissões abrangem os casos Apenas uma vez e Permitir durante o uso do app em que o app é usado em um dispositivo com Android 10 ou versões mais recentes.

Local

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

Confira as classes principais:

  • FusedLocationProviderClient
  • Esse é o componente central da estrutura de localização. Depois de criado, use-o para solicitar atualizações de localização e obter 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 ao FusedLocationProviderClient quando você solicita atualizações de localização.
  • LocationCallback
  • Ele é usado para receber notificações quando a localização do dispositivo for alterada ou não puder mais ser determinada. Isso recebe um LocationResult, em que você pode acessar o Location para salvar no seu banco de dados.

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

4. Adicionar recursos de localização

Este codelab se concentra na opção de localização 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 baseado em permissão já está escrito para você. Se já tiver entendido, pule para a próxima etapa.

Confira a seguir os destaques das permissões (nenhuma ação é necessária para essa parte):

  1. Declare qual permissão você vai usar no AndroidManifest.xml.
  2. Antes de tentar acessar as informações de localização, confira se o usuário deu permissão ao app. Caso seu app ainda não tenha recebido permissão, solicite acesso.
  3. Gerencie a escolha de permissão do usuário. Você pode ver esse código no MainActivity.kt.

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

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

Agora, comece a escrever o código do local.

Analisar as principais variáveis necessárias para atualizações de local

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

arquivo ForegroundOnlyLocationService.kt.

Nenhuma ação é necessária nesta etapa. Você só precisa conferir o bloco de código a seguir, junto com os comentários, para entender as principais classes e variáveis usadas 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 por 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 classe principal para obter atualizações de localização. A variável já está inicializada para você, mas é importante revisar o código para saber como isso acontece. Você adiciona um código aqui mais tarde para solicitar atualizações de localização.

Inicializar a LocationRequest

  1. No módulo base, pesquise por 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 os parâmetros de qualidade extras de serviço necessários para sua solicitação (intervalos, tempo de espera máximo e prioridade).

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

Inicializar o LocationCallback

  1. No módulo base, pesquise por 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)

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

O LocationCallback criado aqui é o callback que o FusedLocationProviderClient vai chamar quando uma nova atualização de local estiver disponível.

No callback, primeiro você acessa o local mais recente usando um objeto LocationResult. Depois disso, notifique o Activity sobre a nova localização usando uma transmissão local (se estiver ativa) ou atualize o Notification se esse serviço estiver sendo executado como um Service em primeiro plano.

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

Inscrever-se para receber notificações sobre 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 por 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.getMainLooper())

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

Você provavelmente reconhece o LocationRequest e o LocationCallback definidos anteriormente. Eles informam ao FusedLocationProviderClient os parâmetros de qualidade de serviço da solicitação e como ela deve chamar quando houver uma atualização. Por fim, o objeto Looper especifica a linha de execução do callback.

Esse código está em uma instrução try/catch. Esse método exige esse bloqueio, porque uma SecurityException ocorre quando o app não tem permissão para acessar as informações de localização.

Cancelar inscrição para receber notificações sobre mudanças no local

Quando o aplicativo não precisar mais do acesso às informações de localização, é importante cancelar a inscrição nas atualizações de local.

  1. No módulo base, pesquise por TODO: Step 1.6, Unsubscribe to location changes no arquivo ForegroundOnlyLocationService.kt.
  2. Adicione o código a seguir 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 do seu LocationCallback. O addOnCompleteListener() fornece o callback para conclusão e executa o Task.

Assim como na etapa anterior, você pode ter notado que esse código está em uma instrução try/catch. Esse método exige esse bloqueio, porque uma SecurityException ocorre 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 de inscrição são chamados. Eles são acionados na classe principal quando o usuário toca no botão. Se quiser vê-lo, confira a classe MainActivity.kt.

Executar app

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

As informações de localização serão exibidas na tela de saída. Esse é um app totalmente funcional para o Android 9.

2ae45c4e297e3681.png

d66089bfb532e993.png

5. Suporte ao Android 10

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

Seu app já é inscrito para receber notificações sobre as mudanças de localização, então não há muito trabalho a fazer.

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

SDK de destino 29

  1. No módulo base, pesquise por TODO: Step 2.1, Target Android 10 and then Android 11. no arquivo build.gradle.
  2. Faça estas mudanças:
  3. Defina targetSdkVersion como 29.

O código vai ficar assim:

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

Depois de fazer isso, será solicitado que você sincronize seu projeto. Clique em Sync Now.

153f70847e0ec320.png

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

Adicionar tipo de serviço em primeiro plano

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

No módulo base, procure 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! O app oferece suporte à localização do Android 10 para "durante o uso" seguindo as práticas recomendadas de localização no Android.

Executar app

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

Tudo deve funcionar como antes, mas agora funciona no Android 10. Se você não aceitou as permissões para os locais antes, agora deve ver a tela de permissões.

6a1029175b467c77.png

c7c1d226e49a121.png

39a262b66a275f66.png

6. Suporte ao Android 11

Nesta seção, você é direcionado ao Android 11.

Ótimas notícias: não é preciso fazer alterações em nenhum arquivo, exceto no arquivo build.gradle.

SDK de destino 11

  1. No módulo base, pesquise por TODO: Step 2.1, Target SDK no arquivo build.gradle.
  2. Faça estas mudanças:
  3. compileSdkVersion a 30
  4. targetSdkVersion a 30

O código vai ficar assim:

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

Depois de fazer isso, será solicitado que você sincronize seu projeto. Clique em Sync Now.

153f70847e0ec320.png

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

Executar app

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

Tudo deve funcionar como antes, mas agora funciona no Android 11. Se você não aceitou as permissões para os locais antes, agora deve ver a tela de permissões.

73d8cc88c5877c25.png

cc98fac6e089bc4.png

7. Estratégias de localização para Android

Ao verificar e solicitar permissões de localização conforme mostrado neste codelab, seu app pode acompanhar o nível de acesso em relação à localização do dispositivo.

Esta página lista algumas das principais práticas recomendadas relacionadas às permissões de localização. Para mais informações sobre como manter as propriedades para proteger os dados, confira as Práticas recomendadas de permissões do app.

Solicitar 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 app, 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 ao local do usuário.

Oferecer suporte à degradação suave se a permissão não for concedida

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

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

8. Parabéns

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

Saiba mais