1. Bem-vindo
Este tutorial faz parte do curso Android avançado em Kotlin. Você obterá o máximo valor deste curso se trabalhar com os tutoriais em sequência, mas isso não é obrigatório. Todos os tutoriais do curso estão listados na página de destino Android avançado em Kotlin.
Introdução
Bem-vindo à lição Android avançado em Kotlin sobre cercas geográficas!
A API de geofencing permite definir perímetros, também conhecidos como geofences, que circundam áreas de interesse. Seu aplicativo recebe uma notificação quando o dispositivo cruza uma geocerca, o que permite que você forneça uma experiência relevante quando os usuários estão dentro da área "cercada".
Por exemplo, um aplicativo de companhia aérea pode definir uma geocerca em torno de um aeroporto quando uma reserva de voo está perto do horário de embarque. Quando o dispositivo cruza a geocerca, o aplicativo pode enviar uma notificação que leva os usuários a uma atividade que lhes permite obter o cartão de embarque.
A API Geofencing usa sensores de dispositivo para detectar com precisão a localização do dispositivo de uma forma que economize bateria. O dispositivo pode estar em um de três estados, ou tipos de transição, relacionados à geocerca.
Tipos de transição de geocerca:
Enter: Indica que o dispositivo entrou na (s) geocerca (s).
Dwell: Indica que o dispositivo entrou e está residindo dentro da (s) geocerca (s) por um determinado período de tempo.
Exit: Indica que o dispositivo saiu da (s) geocerca (s).
A cerca geográfica tem muitas aplicações, incluindo:
- Aplicativos de lembrete, onde você pode obter um lembrete quando você se aproxima de um destino. Por exemplo, você recebe um lembrete para pegar uma receita quando chega perto de sua farmácia.
- Serviços de localização de crianças, em que um pai pode ser notificado se uma criança deixar uma área designada por uma cerca virtual.
- Registro de presença, onde um empregador pode saber quando seus funcionários chegam no momento em que entram em uma geocerca.
- Um aplicativo de caça ao tesouro que usa cercas geográficas para marcar o local onde um tesouro está escondido. Ao entrar nesse perímetro, você será notificado de que ganhou. Este é o aplicativo que você criará neste tutorial!
A imagem abaixo mostra os locais da geocerca denotados por marcadores e os raios ao redor deles.
O que você precisará
- A versão mais recente do Android Studio.
- Um mínimo de SDK API 29 em seu dispositivo ou emulador. (O aplicativo ainda deve funcionar em níveis de API inferiores, mas pode ter uma aparência diferente.)
O que você já deveria saber
- Kotlin, conforme ensinado no Kotlin Bootcamp
- Desenvolvimento básico do Android, conforme ensinado em Desenvolvimento de aplicativos Android com Kotlin
O que você aprenderá
- Como verificar as permissões do usuário.
- Como verificar as configurações do dispositivo.
- Como adicionar receptores de transmissão.
- Como adicionar geocercas.
- Como tratar com as transições da geocerca.
- Como simular locais no emulador.
2. Visão geral do aplicativo
O aplicativo que você criará neste tutorial é um jogo de caça ao tesouro. Este aplicativo é uma caça ao tesouro que dá ao usuário uma pista, e quando o usuário inserir o local correto, o aplicativo irá avisá-lo com a próxima pista, ou uma tela de vitória se ele tiver terminado a caça.
As imagens abaixo mostram uma pista e a tela de vitória.
|
|
Observe que o código do jogo atual tem locais de São Francisco codificados, mas você aprenderá como personalizar o jogo criando suas próprias cercas geográficas para levar as pessoas a lugares em sua área.
3. Começando
Para começar, baixe o código:
Como alternativa, você pode clonar o repositório GitHub para o código e alternar para o branch starter-code
:
$ git clone https://github.com/googlecodelabs/android-kotlin-geo-fences
4. Tarefa: familiarizando-se com o código
Etapa 1: execute o aplicativo inicial
- Execute o aplicativo inicializador em um emulador ou em seu próprio dispositivo. Você deverá ver uma tela inicial com um Android segurando um mapa do tesouro.
Etapa 2: familiarize-se com o código
O aplicativo inicial contém código para ajudá-lo a começar e economizar algum trabalho. Ele contém recurso, layouts, uma atividade, um modelo de vista e um receptor de transmissão que você completará durante esta lição.
Abra as seguintes classes importantes fornecidas para você e familiarize-se com o código:
HuntMainActivity.kt
é a classe principal na qual você trabalhará. Esta classe contém o código básico para funções que tratam de permissões e para adicionar e remover cercas geográficas.GeofenceViewModel.kt
é oViewModel
associado aHuntMainActivity.kt
. Esta classe trata com oGeofenceIndex
LiveData e determina qual dica deve ser mostrada na tela.NotificationUtils.kt
: Quando você entra em uma geocerca, uma notificação é exibida. Esta classe cria e estiliza essa notificação.activity_main.xml
atualmente exibe uma imagem de um Android, mas você irá implementá-la para exibir uma dica para levar seus jogadores ao próximo local.GeofenceBroadcastReceiver.kt
contém o código de esqueleto para o métodoonReceive()
doBroadcastReceiver.
Você irá atualizar oonReceive()
método neste tutorial.
5. Tarefa: Solicitando permissões
A primeira coisa que seu aplicativo precisa fazer é obter permissões de localização do usuário. Isso envolve as seguintes etapas de alto nível e será o mesmo para qualquer aplicativo que você criar e que precise de permissões.
- Adicione as permissões ao manifesto do Android.
- Crie um método que verifique as permissões.
- Solicite essas permissões chamando esse método.
- Lide com o resultado da solicitação de permissões ao usuário.
Etapa 1: adicionar permissões ao AndroidManifest
A API Geofencing exige que o local seja compartilhado o tempo todo. Se você estiver no Android versão Q ou posterior, precisará solicitar especificamente ao usuário essa permissão.
- Abra
AndroidManifest.xml
. - Adicione permissões para
ACCESS_FINE_LOCATION
eACCESS_BACKGROUND_LOCATION
acima da etiquetaapplication
.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
Etapa 2: verifique se o dispositivo está executando o Android Q (API 29) ou posterior
Verifique se o dispositivo está executando o Android Q ou posterior. Para dispositivos que executam o Android Q (API 29) ou posterior, você terá que solicitar uma permissão adicional de localização em segundo plano.
- Abra
HuntMainActivity.kt
. - Acima do método
onCreate()
, adicione uma variável de membro chamadarunningQOrLater
. Isso verificará qual API o dispositivo está executando.
private val runningQOrLater = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q
Etapa 3: crie um método para verificar as permissões
Em seu aplicativo, você precisa verificar se as permissões foram concedidas e, em caso negativo, solicite-as.
- Em
HuntMainActivity
, substitua o código no métodoforegroundAndBackgroundLocationPermissionApproved()
pelo código abaixo, que é explicado posteriormente.
@TargetApi(29)
private fun foregroundAndBackgroundLocationPermissionApproved(): Boolean {
val foregroundLocationApproved = (
PackageManager.PERMISSION_GRANTED ==
ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION))
val backgroundPermissionApproved =
if (runningQOrLater) {
PackageManager.PERMISSION_GRANTED ==
ActivityCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_BACKGROUND_LOCATION
)
} else {
true
}
return foregroundLocationApproved && backgroundPermissionApproved
}
- Primeiro, você deve verificar se a permissão
ACCESS_FINE_LOCATION
foi concedida.
val foregroundLocationApproved = (
PackageManager.PERMISSION_GRANTED ==
ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION))
- Se o dispositivo estiver executando o Android Q (API 29) ou superior, verifique se a permissão
ACCESS_BACKGROUND_LOCATION
foi concedida. Retornetrue
se o dispositivo estiver executando uma versão inferior a Q, onde você não precisa de permissão para acessar o local em segundo plano.
val backgroundPermissionApproved =
if (runningQOrLater) {
PackageManager.PERMISSION_GRANTED ==
ActivityCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_BACKGROUND_LOCATION
)
} else {
true
}
- Retorne
true
se as permissões foram concedidas efalse
se não.
return foregroundLocationApproved && backgroundPermissionApproved
Etapa 4: solicitar permissões
- Copie o seguinte código para o método
requestForegroundAndBackgroundLocationPermissions()
. É aqui que você pede ao usuário para conceder permissões de localização. Cada etapa é explicada nos pontos abaixo.
@TargetApi(29 )
private fun requestForegroundAndBackgroundLocationPermissions() {
if (foregroundAndBackgroundLocationPermissionApproved())
return
var permissionsArray = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)
val resultCode = when {
runningQOrLater -> {
permissionsArray += Manifest.permission.ACCESS_BACKGROUND_LOCATION
REQUEST_FOREGROUND_AND_BACKGROUND_PERMISSION_RESULT_CODE
}
else -> REQUEST_FOREGROUND_ONLY_PERMISSIONS_REQUEST_CODE
}
Log.d(TAG, "Request foreground only location permission")
ActivityCompat.requestPermissions(
this@HuntMainActivity,
permissionsArray,
resultCode
)
}
- Se as permissões já foram concedidas, você não precisa perguntar novamente, então você pode
return
fora do método.
if (foregroundAndBackgroundLocationPermissionApproved())
return
- O
permissionsArray
contém as permissões a serem solicitadas. Inicialmente, adicioneACCESS_FINE_LOCATION
uma vez que é necessário para todos os níveis de API.
var permissionsArray = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)
- Em seguida, você precisa de um
resultCode
. Este código é diferente se o dispositivo está executando Q (API 29) ou posterior e determina se você precisa verificar se há uma permissão (localização precisa) ou várias permissões (localização fina e em segundo plano) quando o usuário retorna da tela de solicitação de permissão. - Adicione uma instrução
when
para verificar a versão em execução e atribuaresultCode
aREQUEST_FOREGROUND_AND_BACKGROUND_PERMISSION_RESULT_CODE
se o dispositivo estiver executando Q (API 29) ou posterior, eREQUEST_FOREGROUND_ONLY_PERMISSIONS_REQUEST_CODE
, se não.
val resultCode = when {
runningQOrLater -> {
permissionsArray += Manifest.permission.ACCESS_BACKGROUND_LOCATION
REQUEST_FOREGROUND_AND_BACKGROUND_PERMISSION_RESULT_CODE
}
else -> REQUEST_FOREGROUND_ONLY_PERMISSIONS_REQUEST_CODE
}
- Por fim, solicite permissões passando na atividade atual, a matriz de permissões e o código de resultado.
ActivityCompat.requestPermissions(
this@HuntMainActivity,
permissionsArray,
resultCode
)
Etapa 5: manipular permissões
Depois que o usuário responde à solicitação de permissão, você precisa tratar com sua resposta em onRequestPermissionsResult()
.
- Copie este código para o método
onRequestPermissionsResult()
.
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
Log.d(TAG, "onRequestPermissionResult")
if (
grantResults.isEmpty() ||
grantResults[LOCATION_PERMISSION_INDEX] == PackageManager.PERMISSION_DENIED ||
(requestCode == REQUEST_FOREGROUND_AND_BACKGROUND_PERMISSION_RESULT_CODE &&
grantResults[BACKGROUND_LOCATION_PERMISSION_INDEX] ==
PackageManager.PERMISSION_DENIED))
{
Snackbar.make(
binding.activityMapsMain,
R.string.permission_denied_explanation,
Snackbar.LENGTH_INDEFINITE
)
.setAction(R.string.settings) {
startActivity(Intent().apply {
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
data = Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
})
}.show()
} else {
checkDeviceLocationSettingsAndStartGeofence()
}
}
- As permissões podem ser negadas de algumas maneiras:
- Se a matriz
grantResults
estiver vazia, a interação foi interrompida e a solicitação de permissão cancelada. - Se o valor do array
grantResults
emLOCATION_PERMISSION_INDEX
tiverPERMISSION_DENIED
, isso significa que o usuário negou as permissões de primeiro plano. - Se o código de solicitação for igual a
REQUEST_FOREGROUND_AND_BACKGROUND_PERMISSION_RESULT_CODE
eBACKGROUND_LOCATION_PERMISSION_INDEX
for negado, isso significa que o dispositivo está executando Q (API 29) ou superior e que as permissões de segundo plano foram negadas.
if (grantResults.isEmpty() ||
grantResults[LOCATION_PERMISSION_INDEX] == PackageManager.PERMISSION_DENIED ||
(requestCode == REQUEST_FOREGROUND_AND_BACKGROUND_PERMISSION_RESULT_CODE &&
grantResults[BACKGROUND_LOCATION_PERMISSION_INDEX] ==
PackageManager.PERMISSION_DENIED))
- Este aplicativo tem muito pouco uso se as permissões não forem concedidas, então apresente uma snackbar explicando ao usuário que o aplicativo precisa de permissões de localização para que eles possam jogar.
Snackbar.make(
binding.activityMapsMain,
R.string.permission_denied_explanation,
Snackbar.LENGTH_INDEFINITE
)
.setAction(R.string.settings) {
startActivity(Intent().apply {
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
data = Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
})
}.show()
- Caso contrário, as permissões foram concedidas e você pode chamar o método
checkDeviceLocationSettingsAndStartGeofence()
.
else {
checkDeviceLocationSettingsAndStartGeofence()
}
- Execute seu aplicativo! Você deverá ver um pop-up solicitando que você conceda permissões. Escolha Allow all the time ou Allow se você estiver executando uma API inferior a 29.
6. Tarefa: Verificar a localização do dispositivo
Seu código agora pede ao usuário para dar permissões.
No entanto, se a localização do dispositivo do usuário estiver desativada, essa permissão não significará nada.
A próxima coisa a verificar é se a localização do dispositivo está ativada. Nesta etapa, você adicionará um código para verificar se o local do dispositivo de um usuário está habilitado e, se não estiver, exibirá uma atividade onde ele pode ativá-lo usando uma solicitação de localização.
- Copie esse código para o método
checkDeviceLocationSettingsAndStartGeofence()
emHuntMainActivity.kt
. As etapas são explicadas nos pontos abaixo.
private fun checkDeviceLocationSettingsAndStartGeofence(resolve:Boolean = true) {
val locationRequest = LocationRequest.create().apply {
priority = LocationRequest.PRIORITY_LOW_POWER
}
val builder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest)
val settingsClient = LocationServices.getSettingsClient(this)
val locationSettingsResponseTask =
settingsClient.checkLocationSettings(builder.build())
locationSettingsResponseTask.addOnFailureListener { exception ->
if (exception is ResolvableApiException && resolve){
try {
exception.startResolutionForResult(this@HuntMainActivity,
REQUEST_TURN_DEVICE_LOCATION_ON)
} catch (sendEx: IntentSender.SendIntentException) {
Log.d(TAG, "Error getting location settings resolution: " + sendEx.message)
}
} else {
Snackbar.make(
binding.activityMapsMain,
R.string.location_required_error, Snackbar.LENGTH_INDEFINITE
).setAction(android.R.string.ok) {
checkDeviceLocationSettingsAndStartGeofence()
}.show()
}
}
locationSettingsResponseTask.addOnCompleteListener {
if ( it.isSuccessful ) {
addGeofenceForClue()
}
}
}
- Primeiro, crie um
LocationRequest
e use-o com umLocationSettingsRequest
Builder
.
val locationRequest = LocationRequest.create().apply {
priority = LocationRequest.PRIORITY_LOW_POWER
}
val builder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest)
- Em seguida, use
LocationServices
para obter oSettingsClient.
Crie umval
chamadolocationSettingsResponseTask
e use-o para verificar as configurações de localização.
val settingsClient = LocationServices.getSettingsClient(this)
val locationSettingsResponseTask =
settingsClient.checkLocationSettings(builder.build())
- Como o caso em que você está mais interessado é descobrir se as configurações de localização não estão satisfeitas, adicione um
onFailureListener()
aolocationSettingsResponseTask
.
locationSettingsResponseTask.addOnFailureListener { exception ->
}
- Verifique se a exceção é do tipo
ResolvableApiException
e, em caso afirmativo, tente chamar o métodostartResolutionForResult()
para solicitar ao usuário que ligue localização do dispositivo.
if (exception is ResolvableApiException && resolve){
try {
exception.startResolutionForResult(this@HuntMainActivity,
REQUEST_TURN_DEVICE_LOCATION_ON)
}
- Se a chamada de
startResolutionForResult
entrar no bloco catch, imprima um log.
catch (sendEx: IntentSender.SendIntentException) {
Log.d(TAG, "Error getting location settings resolution: " + sendEx.message)
}
- Se a exceção não for do tipo
ResolvableApiException
, apresente uma snackbar que alerte o usuário de que o local precisa ser habilitado para jogar a caça ao tesouro.
else {
Snackbar.make(
binding.activityMapsMain,
R.string.location_required_error, Snackbar.LENGTH_INDEFINITE
).setAction(android.R.string.ok) {
checkDeviceLocationSettingsAndStartGeofence()
}.show()
}
- Se a
locationSettingsResponseTask
for concluída, verifique se ela foi bem-sucedida e adicione uma geocerca como pista.
locationSettingsResponseTask.addOnCompleteListener {
if ( it.isSuccessful ) {
addGeofenceForClue()
}
}
- Em
onActivityResult()
, substitua o código existente pelo código abaixo. Depois que o usuário escolhe se aceita ou negar as permissões de localização do dispositivo, verifica se o usuário optou por aceitar as permissões. Se não, pergunte novamente.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_TURN_DEVICE_LOCATION_ON) {
checkDeviceLocationSettingsAndStartGeofence(false)
}
}
- Para testar isso, desligue a localização do dispositivo e execute o aplicativo. Você deverá ver um pop-up conforme mostrado abaixo. Pressione OK.
7. Tarefa: Adicionando geofences
Agora que você concluiu a verificação de que as permissões apropriadas foram concedidas, adicione algumas geocerca!
Etapa 1: criar uma intenção pendente
Você precisa de uma maneira de tratar com as transições da geocerca, o que é feito com um PendingIntent
. Um PendingIntent
é uma descrição de um Intent e uma ação alvo a ser executada com ele. Você criará uma intenção pendente para um BroadcastReceiver
para tratar com as transições da geocerca.
- Em
HuntMainActivity.kt
, acima deonCreate()
, adicione uma variável privada chamadageofencePendingIntent
do tipoPendingIntent
para tratar com o transições de geocerca. ConectegeofencePendingIntent
aoGeofenceTransitionsBroadcastReceiver
.
private val geofencePendingIntent: PendingIntent by lazy {
val intent = Intent(this, GeofenceBroadcastReceiver::class.java)
intent.action = ACTION_GEOFENCE_EVENT
PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
Etapa 2: Adicionar um cliente de cerca geográfica
Um GeofencingClient
é o principal ponto de entrada para interagir com as APIs de geofencing.
- No método
onCreate()
, instancie ogeofencingClient
que já está declarado no código inicial.
geofencingClient = LocationServices.getGeofencingClient(this)
Etapa 3: Adicionar geocerca
- Copie este código para o método
addGeofenceForClue()
. Cada etapa é explicada nos pontos abaixo.
private fun addGeofenceForClue() {
if (viewModel.geofenceIsActive()) return
val currentGeofenceIndex = viewModel.nextGeofenceIndex()
if(currentGeofenceIndex >= GeofencingConstants.NUM_LANDMARKS) {
removeGeofences()
viewModel.geofenceActivated()
return
}
val currentGeofenceData = GeofencingConstants.LANDMARK_DATA[currentGeofenceIndex]
val geofence = Geofence.Builder()
.setRequestId(currentGeofenceData.id)
.setCircularRegion(currentGeofenceData.latLong.latitude,
currentGeofenceData.latLong.longitude,
GeofencingConstants.GEOFENCE_RADIUS_IN_METERS
)
.setExpirationDuration(GeofencingConstants.GEOFENCE_EXPIRATION_IN_MILLISECONDS)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
.build()
val geofencingRequest = GeofencingRequest.Builder()
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
.addGeofence(geofence)
.build()
geofencingClient.removeGeofences(geofencePendingIntent)?.run {
addOnCompleteListener {
geofencingClient.addGeofences(geofencingRequest, geofencePendingIntent)?.run {
addOnSuccessListener {
Toast.makeText(this@HuntMainActivity, R.string.geofences_added,
Toast.LENGTH_SHORT)
.show()
Log.e("Add Geofence", geofence.requestId)
viewModel.geofenceActivated()
}
addOnFailureListener {
Toast.makeText(this@HuntMainActivity, R.string.geofences_not_added,
Toast.LENGTH_SHORT).show()
if ((it.message != null)) {
Log.w(TAG, it.message)
}
}
}
}
}
}
- Primeiro, verifique se você tem alguma geocerca ativa para sua caça ao tesouro. Se já o faz, não deve adicionar outro, pois somente quer que eles procurem um tesouro de cada vez.
if (viewModel.geofenceIsActive()) return
- Encontre o
currentGeofenceIndex
emviewModel
. Remova quaisquer geofences existentes, chamegeofenceActivated
noviewModel
e retorne.
val currentGeofenceIndex = viewModel.nextGeofenceIndex()
if(currentGeofenceIndex >= GeofencingConstants.NUM_LANDMARKS){
removeGeofences()
viewModel.geofenceActivated()
return
}
- Depois de ter o índice da geocerca e saber se ele é válido, obtenha os dados em torno da geocerca, que incluem o id e as coordenadas de latitude e longitude.
val currentGeofenceData = GeofencingConstants.LANDMARK_DATA [currentGeofenceIndex]
- Construa a geofence usando o construtor da geofence e as informações em
currentGeofenceData
. Defina a duração da expiração usando a constante definida emGeofencingConstants
. Defina o tipo de transição paraGEOFENCE_TRANSITION_ENTER
. Finalmente, construa a geocerca.
val geofence = Geofence.Builder()
.setRequestId(currentGeofenceData.id)
.setCircularRegion(currentGeofenceData.latLong.latitude,
currentGeofenceData.latLong.longitude,
GeofencingConstants.GEOFENCE_RADIUS_IN_METERS
)
.setExpirationDuration(GeofencingConstants.GEOFENCE_EXPIRATION_IN_MILLISECONDS)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
.build()
- Crie a solicitação de geocerca. Defina o gatilho inicial para
INITIAL_TRIGGER_ENTER
, adicione a geocerca que acabou de construir e, em seguida, construa.
val geofencingRequest = GeofencingRequest.Builder()
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
.addGeofence(geofence)
.build()
- Chame
removeGeofences()
nogeofencingClient
para remover quaisquer geofences já associadas aoPendingIntent
.
geofencingClient.removeGeofences(geofencePendingIntent)?.run {
}
- Quando
removeGeofences()
for concluído, independentemente de seu sucesso ou falha, adicione as novas geofences. Você pode desconsiderar o sucesso ou o fracasso da remoção das geocerca porque, mesmo que a remoção falhe, não afetará a adição de outra geocerca.
addOnCompleteListener {
geofencingClient.addGeofences(geofencingRequest, geofencePendingIntent)?.run {
}
}
- Se a adição das cercas geográficas for bem-sucedida, avise o usuário com um brinde.
addOnSuccessListener {
Toast.makeText(this@HuntMainActivity, R.string.geofences_added,
Toast.LENGTH_SHORT)
.show()
Log.e("Add Geofence", geofence.requestId)
viewModel.geofenceActivated()
}
- Se a adição das geocercas falhar, apresente um brinde diferente, informando ao usuário que houve um problema ao adicionar as geocerca.
addOnFailureListener {
Toast.makeText(this@HuntMainActivity, R.string.geofences_not_added,
Toast.LENGTH_SHORT).show()
if ((it.message != null)) {
Log.w(TAG, it.message)
}
}
- Execute seu aplicativo. Sua tela deve exibir uma pista e um brinde que informa que a geocerca foi adicionada.
8. Tarefa: Atualizar o receptor de transmissão
Seu aplicativo agora adiciona geofences. No entanto, tente navegar até a Golden Gate Bridge (o local correto para a primeira pista padrão). Nada acontece. Por que é que?
Quando o usuário entra em uma geocerca estabelecida por uma pista, no caso a Ponte Golden Gate, você quer ser avisado, para que possa apresentar a próxima pista. Você pode fazer isso usando um receptor de transmissão que pode receber detalhes sobre os eventos de transição da geocerca.
Os aplicativos Android podem enviar ou receber mensagens de broadcast do sistema Android e outros aplicativos usando Broadcast Receivers. Eles usam o padrão de design publicar-assinar, onde as transmissões são enviadas e os aplicativos podem se registrar para receber transmissões específicas. Quando uma transmissão assinada é enviada, o aplicativo é notificado.
Etapa 1: substituir o método onReceive()
- Em
GeofenceBroadcastReceiver.kt
, encontre a funçãoonReceive()
e copie este código para a classe. Cada etapa é explicada nos pontos abaixo.
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == ACTION_GEOFENCE_EVENT) {
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
val errorMessage = errorMessage(context, geofencingEvent.errorCode)
Log.e(TAG, errorMessage)
return
}
if (geofencingEvent.geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) {
Log.v(TAG, context.getString(R.string.geofence_entered))
val fenceId = when {
geofencingEvent.triggeringGeofences.isNotEmpty() ->
geofencingEvent.triggeringGeofences[0].requestId
else -> {
Log.e(TAG, "No Geofence Trigger Found! Abort mission!")
return
}
}
val foundIndex = GeofencingConstants.LANDMARK_DATA.indexOfFirst {
it.id == fenceId
}
if ( -1 == foundIndex ) {
Log.e(TAG, "Unknown Geofence: Abort Mission")
return
}
val notificationManager = ContextCompat.getSystemService(
context,
NotificationManager::class.java
) as NotificationManager
notificationManager.sendGeofenceEnteredNotification(
context, foundIndex
)
}
}
}
- Um Broadcast Receiver pode receber muitos tipos de ações. Para este aplicativo, você somente precisa saber quando a geocerca foi inserida. Verifique se a ação da intenção é do tipo
ACTION_GEOFENCE_EVENT
.
if (intent.action == ACTION_GEOFENCE_EVENT) {
}
- Crie uma variável chamada
geofencingEvent
e inicialize-a paraGeofencingEvent
com a intenção passada.
val geofencingEvent = GeofencingEvent.fromIntent(intent)
- Se houver um erro, você precisa entender o que deu errado. Salve uma variável com a mensagem de erro obtida através do código de erro das cercas geográficas. Registre essa mensagem e retorne do método.
if (geofencingEvent.hasError()) {
val errorMessage = errorMessage(context, geofencingEvent.errorCode)
Log.e(TAG, errorMessage)
return
}
- Verifique se o tipo
geofenceTransition
éENTER
.
if (geofencingEvent.geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) {}
- Se o array
triggeringGeofences
não estiver vazio, defina ofenceID
para orequestId
da primeira geocerca. Você teria apenas uma geocerca ativa por vez, portanto, se a matriz não estivesse vazia, haveria apenas uma para interagir. Se a matriz estiver vazia, registre uma mensagem ereturn
.
val fenceId = when {
geofencingEvent.triggeringGeofences.isNotEmpty() ->
geofencingEvent.triggeringGeofences[0].requestId
else -> {
Log.e(TAG, "No Geofence Trigger Found! Abort mission!")
return
}
}
- Verifique se a geocerca é consistente com as constantes listadas em
GeofenceUtil.kt
. Caso contrário, imprima um registro ereturn
.
val foundIndex = GeofencingConstants.LANDMARK_DATA.indexOfFirst {
it.id == fenceId
}
if ( -1 == foundIndex ) {
Log.e(TAG, "Unknown Geofence: Abort Mission")
return
}
- Se sua execução de código chegou até aqui, o usuário inseriu uma geocerca válida. Envie uma notificação contando a eles as boas notícias!
val notificationManager = ContextCompat.getSystemService(
context,
NotificationManager::class.java
) as NotificationManager
notificationManager.sendGeofenceEnteredNotification(
context, foundIndex
)
- Tente você mesmo entrando em uma geocerca ou emulando sua localização para estar na geocerca (instruções na próxima etapa). Quando você entra, uma notificação deve aparecer.
9. Tarefa: Simular um local no emulador
Pule esta seção se você não estiver usando um emulador.
Como o teste deste tutorial depende de uma caminhada, pode ser mais conveniente usar um local simulado no emulador. Nesta tarefa, você aprenderá como simular a localização em seu emulador.
Etapa 1: simule sua localização
- Na barra de menus ao lado do emulador, toque nos três pontos (...) na parte inferior para abrir o plano de Extended controls.
- Selecione Location.
- Na barra de pesquisa do mapa, insira um local, como a Ponte Golden Gate. O marcador de local é exibido no local que você inseriu.
|
|
- No canto inferior direito do painel, pressione o botão Set Location.
- Vá para o aplicativo do Google Maps e a notificação deve aparecer. Isso pode demorar alguns segundos.
10. Tarefa: Removendo geocerca
Quando você não precisar mais de cercas geográficas, é uma prática recomendada removê-las, o que interrompe o monitoramento, para economizar bateria e ciclos de CPU.
Etapa 1: remover geofences
- Em
HuntMainActivity.kt
, copie este código para o métodoremoveGeofences()
. Cada etapa é explicada nos pontos abaixo.
private fun removeGeofences() {
if (!foregroundAndBackgroundLocationPermissionApproved()) {
return
}
geofencingClient.removeGeofences(geofencePendingIntent)?.run {
addOnSuccessListener {
Log.d(TAG, getString(R.string.geofences_removed))
Toast.makeText(applicationContext, R.string.geofences_removed, Toast.LENGTH_SHORT)
.show()
}
addOnFailureListener {
Log.d(TAG, getString(R.string.geofences_not_removed))
}
}
}
- Inicialmente, verifique se as permissões de primeiro plano foram aprovadas. Se não, volte.
if (!foregroundAndBackgroundLocationPermissionApproved()) {
return
}
- Chame
removeGeofences()
nogeofencingClient
e passe ogeofencePendingIntent
.
geofencingClient.removeGeofences(geofencePendingIntent)?.run {
}
- Adicione um
onSuccessListener()
e informe ao usuário com um brinde que as cercas geográficas foram removidas com sucesso.
addOnSuccessListener {
Log.d(TAG, getString(R.string.geofences_removed))
Toast.makeText(applicationContext, R.string.geofences_removed, Toast.LENGTH_SHORT)
.show()
}
- Adicione um
onFailureListener()
onde você registra se as cercas geográficas não foram removidas.
addOnFailureListener {
Log.d(TAG, getString(R.string.geofences_not_removed))
}
- O método
removeGeofences()
é chamado no métodoonDestroy()
incluído no código inicial.
11. Tarefa: Navegar até o local vencedor
Agora que tudo está configurado, resta apenas uma coisa a fazer. Ganhar o jogo!
Etapa 1: Ganhe o jogo!
Navegue até o local vencedor zombando do local em seu emulador ou caminhando fisicamente até lá! Parabéns, você ganhou este tutorial!
12. Desafio de codificação
Você pode adicionar pontos de referência para personalizar sua caça ao tesouro e adicionar mais cercas geográficas para fazer a caça ao tesouro durar mais tempo.
- Em
strings.xml
, adicione sua dica personalizada e local.
<!-- Geofence Hints -->
<string name="lombard_street_hint">Go to the most crooked street in the City</string>
<!-- Geofence Locations -->
<string name="lombard_street_location">at Lombard Street</string>
- Em
GeofenceUtils.kt
, personalize os pontos de referência criando umLandmarkDataObject
com um ID de destino, dica de destino, localização de destino e latitude e longitude de destino. Adicione-o ao arrayLANDMARK_DATA
com seus próprios objetos de referência.
val LANDMARK_DATA = arrayOf(
LandmarkDataObject(
"Lombard street",
R.string.lombard_street_hint,
R.string.lombard_street_location,
LatLng(37.801205, -122.426752))
)
13. Resumo
Neste tutorial você aprendeu como:
- Adicione permissões, solicite permissões, verifique as permissões e controle as permissões.
- Verifique a localização do dispositivo usando o cliente de configurações.
- Adicione geofences usando uma intenção pendente e um cliente de geofencing.
- Integre um receptor de transmissão para detectar quando uma geocerca é inserida substituindo o método
onReceive()
. - Remova geofences usando o cliente de geofencing.
14. Saiba mais
Cursos Udacity:
Documentação do desenvolvedor Android:
Outros recursos:
15. Próximo tutorial
Para obter links para outros tutoriais neste curso, consulte a página inicial de tutoriais do Android avançado em Kotlin.