Sobre este codelab
1. Antes de começar
Imagine que você está em um shopping cheio e encontrou sua amiga Joana. Seu primeiro instinto pode ser de acenar e gritar para tentar chamar a atenção de Joana. A API Nearby Messages do Google foi criada para possibilitar que seu app chame a atenção dos usuários e facilite o encontro de amigos quando estiverem próximos. Este codelab ensina como usar a API Nearby Messages para possibilitar interações do usuário com base na proximidade física. Para simplificar o codelab, cada usuário publicará o modelo do build do próprio smartphone: android.os.Build.MODEL
. Mas, na realidade, cada usuário pode publicar o próprio userId
ou outras informações adequadas ao seu caso de uso para amigos que estejam por perto. A API Nearby Messages combina conectividade com a Internet, Bluetooth e outras tecnologias para fornecer esse recurso.
Pré-requisitos
- Ter conhecimento básico de desenvolvimento Android e Kotlin.
- Saber como criar e executar apps no Android Studio.
- Ter dois ou mais dispositivos Android para executar e testar o código.
O que você aprenderá
- Como adicionar a biblioteca Nearby ao seu app.
- Como transmitir mensagens para as partes interessadas.
- Como detectar mensagens de pontos de interesse.
- Como gerar uma chave de API para suas mensagens.
- Práticas recomendadas para a duração da bateria.
O que é necessário
- Uma Conta do Google (por exemplo, endereço do Gmail) para conseguir uma chave de API do Google.
- A versão mais recente do Android Studio.
- Dois dispositivos Android com o Google Play Services (em outras palavras, a Play Store) instalados neles.
- Uma conexão de Internet (diferente da API Nearby Connections que não exige isso).
O que você vai criar
Um único app Activity
que permite ao usuário publicar informações de dispositivos e receber informações sobre dispositivos por perto. O app tem duas chaves que o usuário pode alternar: a primeira é para descobrir ou interromper a descoberta de mensagens próximas; a segunda é para publicar ou cancelar a publicação de mensagens. Para esse app, queremos que a publicação e a descoberta sejam interrompidas após 120 segundos arbitrários. Para isso, vamos falar um pouco mais sobre a API, criar objetos PublishOptions
e SubscribeOptions
e usar os callbacks onExpired()
deles para desativar as chaves de IU de publicação e assinatura.
2. Criar um projeto do Android Studio
- Inicie um novo projeto no Android Studio.
- Escolha Empty Activity.
- Nomeie o projeto como Nearby Messages Example e defina a linguagem como Kotlin.
3. Configurar o código
- Adicione a versão mais recente da dependência Nearby ao arquivo
build.gradle
do seu app. Isso possibilita o uso da API Nearby Messages para enviar e detectar mensagens de dispositivos por perto.
implementation 'com.google.android.gms:play-services-nearby:18.0.0'
- Defina a opção de build viewBinding como
true
no bloco do Android para ativar a ViewBinding.
android {
...
buildFeatures {
viewBinding true
}
}
- Clique em Sync Now ou no botão de martelo verde para que o Android Studio registre essas mudanças no Gradle.
- Adicione as opções "Discover nearby devices" e "Share device information" e a RecyclerView que conterá a lista de dispositivos. No arquivo
activity_main.xml
, substitua o código pelo seguinte.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".MainActivity">
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/subscribe_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Discover nearby devices" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/publish_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Share device information" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/nearby_msg_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:transcriptMode="alwaysScroll" />
</LinearLayout>
Observação: no seu projeto, mude valores como 16dp
para recursos como @dimen/activity_vertical_margin
.
4. Adicionar a Nearby Messages ao app
Definir variáveis
- Dentro da atividade principal (
MainActivity.kt
), acima da funçãoonCreate()
, defina as seguintes variáveis colando este snippet de código.
/**
* For accessing layout variables
*/
private lateinit var binding: ActivityMainBinding
/**
* Sets the time to live in seconds for the publish or subscribe.
*/
private val TTL_IN_SECONDS = 120 // Two minutes.
/**
* Choose of strategies for publishing or subscribing for nearby messages.
*/
private val PUB_SUB_STRATEGY = Strategy.Builder().setTtlSeconds(TTL_IN_SECONDS).build()
/**
* The [Message] object used to broadcast information about the device to nearby devices.
*/
private lateinit var message: Message
/**
* A [MessageListener] for processing messages from nearby devices.
*/
private lateinit var messageListener: MessageListener
/**
* MessageAdapter is a custom class that we will define later. It's for adding
* [messages][Message] to the [RecyclerView]
*/
private lateinit var msgAdapter: MessageAdapter
Definimos uma Strategy
porque queremos personalizar a duração de uma transmissão. Para este codelab, escolhemos 120 segundos. Se você não especificar uma estratégia, a API usará as opções padrão. Neste codelab, usamos a mesma Strategy
para publicação e assinatura, mas isso não é obrigatório.
- Mude a função
onCreate()
para transmitir o objeto ViewBinding parasetContentView()
. O conteúdo do arquivo de layoutactivity_main.xml
será exibido.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
Conectar botões da IU
Este app fará três coisas: publicar e descobrir mensagens com facilidade, além de exibi-las em uma RecyclerView
.
- Queremos que o usuário publique e cancele a publicação de mensagens e descubra (ou seja, assine) mensagens. Por enquanto, crie métodos de stub para isso, chamados
publish()
,unpublish()
,subscribe()
eunsubscribe()
. Criaremos a implementação em uma etapa futura.
private fun publish() {
TODO("Not yet implemented")
}
private fun unpublish() {
TODO("Not yet implemented")
}
private fun subscribe() {
TODO("Not yet implemented")
}
private fun unsubscribe() {
TODO("Not yet implemented")
}
- O usuário pode publicar ou descobrir mensagens (ou seja, fazer assinatura) usando as chaves adicionadas ao layout da atividade. Conecte as duas
Switches
para chamar os métodos que definimos no final da funçãoonCreate()
.
binding.subscribeSwitch.setOnCheckedChangeListener { buttonView, isChecked ->
if (isChecked) {
subscribe()
} else {
unsubscribe()
}
}
binding.publishSwitch.setOnCheckedChangeListener { buttonView, isChecked ->
if (isChecked) {
publish()
} else {
unpublish()
}
}
- Agora que você adicionou o código da IU para publicar e descobrir mensagens, configure a
RecyclerView
para exibir e remover mensagens. ARecyclerView
vai exibir as mensagens que estão sendo publicadas ativamente. O assinante procurará por elas. Quando uma mensagem for encontrada, o assinante a adicionará àRecyclerView
. Quando uma mensagem for perdida, ou seja, quando o criador cancelar a publicação dela, o assinante a removerá daRecyclerView
.
private fun setupMessagesDisplay() {
msgAdapter = MessageAdapter()
with(binding.nearbyMsgRecyclerView) {
layoutManager = LinearLayoutManager(context)
this.adapter = msgAdapter
}
}
class MessageAdapter : RecyclerView.Adapter<MessageAdapter.MessageVH>() {
private var itemsList: MutableList<String> = arrayListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageVH {
return MessageVH(TextView(parent.context))
}
override fun onBindViewHolder(holder: MessageVH, position: Int) {
holder.bind(getItem(position))
}
override fun getItemCount(): Int = itemsList.size
private fun getItem(pos: Int): String? = if (itemsList.isEmpty()) null else itemsList[pos]
fun addItem(item: String) {
itemsList.add(item)
notifyItemInserted(itemsList.size)
}
fun removeItem(item: String) {
val pos = itemsList.indexOf(item)
itemsList.remove(item)
notifyItemRemoved(pos)
}
inner class MessageVH(private val tv: TextView) : RecyclerView.ViewHolder(tv) {
fun bind(item: String?) {
item?.let { tv.text = it }
}
}
}
- No final da função
onCreate()
, adicione uma chamada para a funçãosetupMessagesDisplay()
.
override fun onCreate(savedInstanceState: Bundle?) {
...
setupMessagesDisplay()
}
Agora que a IU está configurada, podemos começar a publicar mensagens para outros dispositivos por perto descobrirem. Veja como seu app ficará no exemplo abaixo:
Adicionar o código de publicação e descoberta
- Para enviar uma mensagem, primeiro precisamos de um objeto
Message
. Como esta é uma demonstração, vamos enviar apenas o modelo do dispositivo. Adicione este código à funçãoonCreate()
para criar a mensagem a ser enviada.
override fun onCreate(savedInstanceState: Bundle?) {
...
// The message being published is simply the Build.MODEL of the device. But since the
// Messages API is expecting a byte array, you must convert the data to a byte array.
message = Message(Build.MODEL.toByteArray(Charset.forName("UTF-8")))
}
- Para publicar uma
Message
que possa ser descoberta por outros dispositivos por perto, basta chamarNearby.getMessagesClient(activity).publish(message)
. No entanto, recomendamos que você vá além e crie seu próprio objetoPublishOptions
. Ele possibilita que você especifique sua própriaStrategy
e aproveite oPublishCallback
, que notificará quando uma mensagem publicada expirar. No código a seguir, criamos uma opção para desativar a chave do usuário quando oTTL
publicado expirar. Em seguida, transmitimos a opção quando chamamospublish()
. Atualize a funçãopublish()
para o seguinte.
private fun publish() {
val options = PublishOptions.Builder()
.setStrategy(PUB_SUB_STRATEGY)
.setCallback(object : PublishCallback() {
override fun onExpired() {
super.onExpired()
// flick the switch off since the publishing has expired.
// recall that we had set expiration time to 120 seconds
// Use runOnUiThread to force the callback
// to run on the UI thread
runOnUiThread{
binding.publishSwitch.isChecked = false
}
}
}).build()
Nearby.getMessagesClient(this).publish(message, options)
}
Esse código é executado sempre que o usuário ativa a chave de publicação.
- Enquanto a publicação exige uma
Message
, a assinatura exige umMessageListener
. No entanto, recomendamos também que você crie um objetoSubscribeOptions
, mesmo que ele não seja necessário para a API funcionar. Criar sua própriaSubscriptionOption
possibilita, por exemplo, especificar por quanto tempo você quer ficar no modo de descoberta.
Adicione o seguinte código MessageListener
à função onCreate()
. Quando uma mensagem for detectada, o listener a adicionará à RecyclerView
. Quando uma mensagem for perdida, o listener a removerá da RecyclerView
.
messageListener = object : MessageListener() {
override fun onFound(message: Message) {
// Called when a new message is found.
val msgBody = String(message.content)
msgAdapter.addItem(msgBody)
}
override fun onLost(message: Message) {
// Called when a message is no longer detectable nearby.
val msgBody = String(message.content)
msgAdapter.removeItem(msgBody)
}
}
- Tecnicamente, um assinante não precisa de um
TTL
. Em vez disso, ele pode definir o próprioTTL
como infinity. No entanto, neste codelab, queremos interromper a descoberta após 120 segundos. Consequentemente, criaremos nossas própriasSubscribeOptions
e usaremos o callbackonExpired()
para desativar aSwitch
da IU de assinatura. Atualize sua função de assinatura com este código.
private fun subscribe() {
val options = SubscribeOptions.Builder()
.setStrategy(PUB_SUB_STRATEGY)
.setCallback(object : SubscribeCallback() {
override fun onExpired() {
super.onExpired()
// flick the switch off since the subscribing has expired.
// recall that we had set expiration time to 120 seconds
// Use runOnUiThread to force the callback
// to run on the UI thread
runOnUiThread {
binding.subscribeSwitch.isChecked = false
}
}
}).build()
Nearby.getMessagesClient(this).subscribe(messageListener, options)
}
- É importante autorizar que os usuários desativem o compartilhamento de informações. Isso significa que os criadores podem suspender a publicação e os assinantes podem cancelar a assinatura. O criador precisa especificar qual mensagem ele quer deixar de publicar. Portanto, se você tiver dez mensagens sendo transmitidas, poderá suspender a publicação de uma, mantendo as outras nove.
private fun unpublish() {
Nearby.getMessagesClient(this).unpublish(message)
}
- Embora um app possa publicar várias mensagens ao mesmo tempo, ele só pode ter um
MessageListener
por vez. Por isso, o cancelamento da assinatura é mais geral. Para fazer esse cancelamento, o assinante precisa especificar o listener.
private fun unsubscribe() {
Nearby.getMessagesClient(this).unsubscribe(messageListener)
}
- Observação: a API costuma interromper os processos quando o do cliente é encerrado. Mas convém cancelar as assinaturas e publicar, se for conveniente, no método do ciclo de vida
onDestroy()
.
override fun onDestroy() {
super.onDestroy()
// although the API should shutdown its processes when the client process dies,
// you may want to stop subscribing (and publishing if convenient)
Nearby.getMessagesClient(this).unpublish(message)
Nearby.getMessagesClient(this).unsubscribe(messageListener)
}
Neste ponto, nosso código será compilado. No entanto, o app não funcionará como esperado porque não tem a chave de API. A MainActivity
vai ficar assim:
package com.example.nearbymessagesexample
import android.os.Build
import android.os.Bundle
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.nearbymessagesexample.databinding.ActivityMainBinding
import com.google.android.gms.nearby.Nearby
import com.google.android.gms.nearby.messages.Message
import com.google.android.gms.nearby.messages.MessageListener
import com.google.android.gms.nearby.messages.PublishCallback
import com.google.android.gms.nearby.messages.PublishOptions
import com.google.android.gms.nearby.messages.Strategy
import com.google.android.gms.nearby.messages.SubscribeCallback
import com.google.android.gms.nearby.messages.SubscribeOptions
import java.nio.charset.Charset
class MainActivity : AppCompatActivity() {
/**
* For accessing layout variables
*/
private lateinit var binding: ActivityMainBinding
/**
* Sets the time to live in seconds for the publish or subscribe.
*/
private val TTL_IN_SECONDS = 120 // Two minutes.
/**
* Choose of strategies for publishing or subscribing for nearby messages.
*/
private val PUB_SUB_STRATEGY = Strategy.Builder().setTtlSeconds(TTL_IN_SECONDS).build()
/**
* The [Message] object used to broadcast information about the device to nearby devices.
*/
private lateinit var message: Message
/**
* A [MessageListener] for processing messages from nearby devices.
*/
private lateinit var messageListener: MessageListener
/**
* For adding [messages][Message] to the [RecyclerView]
*/
private lateinit var msgAdapter: MessageAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.subscribeSwitch.setOnCheckedChangeListener { buttonView, isChecked ->
if (isChecked) {
subscribe()
} else {
unsubscribe()
}
}
binding.publishSwitch.setOnCheckedChangeListener { buttonView, isChecked ->
if (isChecked) {
publish()
} else {
unpublish()
}
}
setupMessagesDisplay()
// The message being published is simply the Build.MODEL of the device. But since the
// Messages API is expecting a byte array, you must convert the data to a byte array.
message = Message(Build.MODEL.toByteArray(Charset.forName("UTF-8")))
messageListener = object : MessageListener() {
override fun onFound(message: Message) {
// Called when a new message is found.
val msgBody = String(message.content)
msgAdapter.addItem(msgBody)
}
override fun onLost(message: Message) {
// Called when a message is no longer detectable nearby.
val msgBody = String(message.content)
msgAdapter.removeItem(msgBody)
}
}
}
override fun onDestroy() {
super.onDestroy()
// although the API should shutdown its processes when the client process dies,
// you may want to stop subscribing (and publishing if convenient)
Nearby.getMessagesClient(this).unpublish(message)
Nearby.getMessagesClient(this).unsubscribe(messageListener)
}
private fun publish() {
val options = PublishOptions.Builder()
.setStrategy(PUB_SUB_STRATEGY)
.setCallback(object : PublishCallback() {
override fun onExpired() {
super.onExpired()
// flick the switch off since the publishing has expired.
// recall that we had set expiration time to 120 seconds
runOnUiThread {
binding.publishSwitch.isChecked = false
}
runOnUiThread() {
binding.publishSwitch.isChecked = false
}
}
}).build()
Nearby.getMessagesClient(this).publish(message, options)
}
private fun unpublish() {
Nearby.getMessagesClient(this).unpublish(message)
}
private fun subscribe() {
val options = SubscribeOptions.Builder()
.setStrategy(PUB_SUB_STRATEGY)
.setCallback(object : SubscribeCallback() {
override fun onExpired() {
super.onExpired()
runOnUiThread {
binding.subscribeSwitch.isChecked = false
}
}
}).build()
Nearby.getMessagesClient(this).subscribe(messageListener, options)
}
private fun unsubscribe() {
Nearby.getMessagesClient(this).unsubscribe(messageListener)
}
private fun setupMessagesDisplay() {
msgAdapter = MessageAdapter()
with(binding.nearbyMsgRecyclerView) {
layoutManager = LinearLayoutManager(context)
this.adapter = msgAdapter
}
}
class MessageAdapter : RecyclerView.Adapter<MessageAdapter.MessageVH>() {
private var itemsList: MutableList<String> = arrayListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageVH {
return MessageVH(TextView(parent.context))
}
override fun onBindViewHolder(holder: MessageVH, position: Int) {
holder.bind(getItem(position))
}
override fun getItemCount(): Int = itemsList.size
private fun getItem(pos: Int): String? = if (itemsList.isEmpty()) null else itemsList[pos]
fun addItem(item: String) {
itemsList.add(item)
notifyItemInserted(itemsList.size)
}
fun removeItem(item: String) {
val pos = itemsList.indexOf(item)
itemsList.remove(item)
notifyItemRemoved(pos)
}
inner class MessageVH(private val tv: TextView) : RecyclerView.ViewHolder(tv) {
fun bind(item: String?) {
item?.let { tv.text = it }
}
}
}
}
Adicionar uma chave de API do Google ao arquivo de manifesto
A API Nearby Messages tem um componente de servidor oferecido pelo Google. Quando você publica uma mensagem, a API Nearby Messages envia a mensagem para o servidor do Google, onde um assinante pode consultá-la. Para que o Google reconheça seu app, adicione a API_KEY
do Google ao arquivo Manifest.xml
. Depois disso, podemos simplesmente iniciar e nos divertir com nosso app.
Para conseguir a chave de API, siga estas três etapas:
- Acesse o Google Developers Console.
- Clique em + Criar credenciais e selecione Chave de API*.*
- Copie a chave de API criada e cole-a no arquivo de manifesto do seu projeto Android.
Adicione o item de metadados com.google.android.nearby.messages.API_KEY
do aplicativo ao arquivo de manifesto. O arquivo vai ficar como o exemplo abaixo.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.nearbymessagesexample">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.NearbyMessagesExample">
<meta-data
android:name="com.google.android.nearby.messages.API_KEY"
android:value="ADD_KEY_HERE" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Depois de adicionar a chave de API, execute o app em dois ou mais dispositivos para ver como eles se comunicam um com o outro.
5. Práticas recomendadas para a duração da bateria
- Para proteger a privacidade do usuário e preservar a duração da bateria, é recomendável parar de publicar e assinar assim que o usuário sair do recurso em que a funcionalidade é necessária.
- Você deve usar a API Nearby Messages para estabelecer a proximidade entre dispositivos, mas não para uma comunicação contínua. Ela pode esgotar as baterias dos dispositivos a uma taxa 2,5 a 3,5 vezes maior que durante o consumo normal.
6. Parabéns
Parabéns! Agora você sabe enviar e descobrir mensagens entre dispositivos próximos usando a API Nearby Messages.
Em resumo, para usar a API Nearby Messages, você precisa adicionar a dependência do play-services-nearby
, buscar uma chave de API no Google Developers Console e adicioná-la ao arquivo Manifest.xml
. A API exige conexão de Internet para que os criadores possam enviar ao servidor do Google mensagens que vão ser descobertas pelos assinantes.
- Você aprendeu a enviar mensagens.
- Você aprendeu a assinar para receber mensagens.
- Você aprendeu a usar as mensagens (nesse caso, elas foram simplesmente mostradas em uma
RecyclerView
).
Qual é a próxima etapa?
Confira nossa série de postagens do blog e um exemplo.