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

1. Antes de começar

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

Quando um app que opera 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)
  • Uma única 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 como oferecer suporte à localização em qualquer versão do Android, principalmente no Android 10 e 11. Ao final do codelab, você terá um app que segue as práticas recomendadas atuais para recuperar atualizações de local.

Pré-requisitos

O que você aprenderá

  • Siga as práticas recomendadas para localização no Android.
  • Processar permissões de localização em primeiro plano (quando o usuário solicita que o app acesse a localização do dispositivo enquanto ele está em uso).
  • Modifique um app existente para adicionar suporte à solicitação de acesso à localização. Para isso, adicione código para se inscrever e cancelar a inscrição na localização.
  • Adicione suporte ao app para Android 10 e 11 incluindo 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 com 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 criar com base neste 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 a página do GitHub diretamente.

Se você não tiver o Git, faça o download do projeto como um arquivo ZIP:

Importar o projeto

Abra o Android Studio, 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 semelhante à imagem abaixo se estiver na visualização Android. Se você estiver na visualização Project, precisará expandi-la para ver a mesma coisa.

fa825dae96c5dc18.png

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 de baixo do Android Studio:

c2273e7835c0841a.png

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 uma mensagem que diz Atualizar para que as mudanças de idioma entrem em vigor? ou algo semelhante, selecione Sim.

Entender o projeto inicial

Você já pode 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. Quando você terminar 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 sair da atividade do app. Adicione o código de 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 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. Verifique se o dispositivo está executando o Android 10 ou uma versão mais recente.
  2. Na barra de ferramentas, selecione a configuração base no seletor suspenso e clique em Executar:

99600e9d44527ab.png

  1. Observe o seguinte app aparecer no seu dispositivo:

99bf1dae46f99af3.png

Nenhuma informação de local aparece na tela de saída. Isso porque você ainda não adicionou o código do local.

3. Adicionar 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, é bom revisar o básico.

Tipos de acesso à localização

Você deve se lembrar das quatro opções diferentes de acesso à localização do 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 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 somente enquanto o app está sendo usado ativamente. Um app é considerado ativo se uma das seguintes condições for verdadeira:
  • Uma atividade está visível.
  • Um serviço em primeiro plano está sendo executado com uma notificação em andamento.
  • Apenas uma vez
  • Adicionada no Android 11, essa opção é igual a 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 o acesso à localização o tempo todo, mas requer uma permissão extra para o Android 10 e versões mais recentes. Você também precisa ter um caso de uso válido e obedecer às políticas de local. Não vamos abordar essa opção neste codelab, porque é 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 o exemplo LocationUpdatesBackgroundKotlin.

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

Para oferecer suporte total às atualizações de localização Permitir somente durante o uso do app, você precisa considerar quando o usuário sai do app. Se quiser continuar recebendo atualizações nessa situação, crie um Service em primeiro plano e o associe 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. Navegue pela 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, é preciso solicitar 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 no tempo de execução.

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

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.

Confira as principais classes:

  • FusedLocationProviderClient
  • Esse é o componente central do framework de localização. Depois de criado, ele é usado para solicitar atualizações de localização e receber o último local conhecido.
  • LocationRequest
  • É um objeto de dados que contém parâmetros de Qualidade de Serviço para solicitações (Intervalos de atualizações, Prioridades e acurácia). Ele é transmitido para o FusedLocationProviderClient quando você solicita atualizações de local.
  • LocationCallback
  • Usado para receber notificações quando a localização do dispositivo muda ou não pode mais ser determinada. Isso é transmitido como um LocationResult em que você pode receber o Location para salvar no seu banco de dados.

Agora que você tem uma ideia básica do que está fazendo, comece a programar.

4. Adicionar recursos de local

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

Para receber atualizações de local, 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, não como solicitar permissões de localização. Por isso, o código baseado em permissões já está escrito para você. Pule se você já entender.

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

  1. Declare qual permissão você usa no AndroidManifest.xml.
  2. Antes de tentar acessar informações de localização, verifique se o usuário deu permissão para isso. Se o app ainda não tiver recebido permissão, solicite o acesso.
  3. Processar a escolha de permissão do usuário. (Você pode ver esse código em MainActivity.kt.)

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

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

Agora, comece a escrever um código de local.

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

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

arquivo ForegroundOnlyLocationService.kt.

Nenhuma ação é necessária nesta etapa. Basta analisar 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 local.

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

Analise a inicialização do FusedLocationProviderClient

No módulo base, procure 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, essa é a classe principal para receber atualizações de local. A variável já está inicializada para você, mas é importante revisar o código para entender como ela é inicializada. Você vai adicionar algum código aqui mais tarde para solicitar atualizações de localização.

Inicializar o LocationRequest

  1. No módulo base, procure 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 extras de qualidade de serviço necessários para sua solicitação (intervalos, tempo máximo de espera 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 funciona.

Inicializar o LocationCallback

  1. No módulo base, procure 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 que você cria aqui é o callback que o FusedLocationProviderClient vai chamar quando uma nova atualização do local estiver disponível.

No callback, primeiro receba o local mais recente usando um objeto LocationResult. Depois disso, notifique seu Activity sobre o novo local 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 mudanças de local

Agora que você inicializou tudo, precisa informar ao FusedLocationProviderClient que quer receber atualizações.

  1. No módulo base, procure 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 os LocationRequest e LocationCallback que definiu anteriormente. Eles informam ao FusedLocationProviderClient os parâmetros de qualidade de serviço da sua solicitação e o que ele deve 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á dentro de uma instrução try/catch. Esse método exige um bloco porque um SecurityException ocorre quando o app não tem permissão para acessar informações de localização.

Cancelar a inscrição em mudanças de local

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

  1. No módulo base, procure 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 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 um bloco porque um SecurityException ocorre quando o app não tem permissão para acessar informações de localização.

Talvez você se pergunte 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 ver, 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 local vão aparecer na tela de saída. Este é um app totalmente funcional para o Android 9.

2ae45c4e297e3681.png

d66089bfb532e993.png

5. Compatibilidade com o Android 10

Nesta seção, você vai adicionar suporte ao Android 10.

Seu app já se inscreve para receber notificações de mudanças de local, então não há muito trabalho a fazer.

Na verdade, basta especificar que o serviço em primeiro plano é usado para fins de localização.

SDK de destino 29

  1. No módulo base, procure 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 disso, você vai precisar sincronizar o 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 receber informações de localização.

No módulo base, pesquise TODO: 2.2, Add foreground service type no arquivo 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 oferece suporte à localização do Android 10 "durante o uso" seguindo as práticas recomendadas para localização no Android.

Executar app

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

Tudo vai funcionar como antes, mas agora no Android 10. Se você não aceitou as permissões de locais antes, a tela de permissão vai aparecer agora.

6a1029175b467c77.png

c7c1d226e49a121.png

39a262b66a275f66.png

6. Compatibilidade com o Android 11

Nesta seção, você vai direcionar o app ao Android 11.

A boa notícia é que você não precisa fazer mudanças em nenhum arquivo, exceto o build.gradle.

SDK de destino 11

  1. No módulo base, procure 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 disso, você vai precisar sincronizar o 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 clique no botão.

Tudo vai funcionar como antes, mas agora no Android 11. Se você não aceitou as permissões de locais antes, a tela de permissão vai aparecer agora.

73d8cc88c5877c25.png

cc98fac6e089bc4.png

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

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

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

Solicitar somente 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 mais recente e 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.

Compatibilidade com 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 de maneira eficaz com as seguintes situações:

  • Seu app não tem acesso a informações de localização.
  • Seu app não tem acesso a informações de localização ao ser 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