1. Antes de comenzar
Imagina que estás en un centro comercial muy concurrido y encuentras a tu amiga Luisa. Tu inclinación podría ser agitar la mano y gritar para llamar la atención de Luisa. La API de Nearby Messages de Google está aquí para que tu app se encargue de llamar la atención a nombre de tus usuarios y permitir que los amigos puedan encontrarse con facilidad cuando estén cerca físicamente. En este codelab, aprenderás a usar la API de Nearby Messages para habilitar las interacciones de usuarios en función de la proximidad física. Con el objetivo de hacer el codelab más sencillo, cada usuario publicará el modelo de compilación de su teléfono: android.os.Build.MODEL
. Sin embargo, en realidad, podrías hacer que cada usuario publicara su userId
, o cualquier información adecuada de tu caso de uso, para amigos cercanos. Para ofrecer esta función, la API de Nearby Messages combina la conectividad a Internet, Bluetooth y otras tecnologías.
Requisitos previos
- Conocimientos básicos sobre desarrollo en Kotlin y para Android
- Cómo crear y ejecutar apps en Android Studio
- Dos o más dispositivos Android para ejecutar y probar el código
Qué aprenderás
- Cómo agregar la biblioteca de Nearby a la app
- Cómo transmitir mensajes a grupos interesados
- Cómo detectar mensajes de lugares de interés
- Cómo obtener una clave de API para tus mensajes
- Prácticas recomendadas para la duración de batería
Requisitos
- Una Cuenta de Google (es decir, una dirección de Gmail) para obtener una clave de API de Google
- La versión más reciente de Android Studio
- Dos dispositivos Android con los Servicios de Google Play (en otras palabras, Play Store) instalados
- Una conexión a Internet (a diferencia de la API de Nearby Connections, que no la requiere)
Qué compilarás
Una sola app de Activity
que permite al usuario publicar información del dispositivo y recibir datos sobre dispositivos cercanos. La app tiene dos interruptores que el usuario puede activar o desactivar: el primero sirve para descubrir o dejar de descubrir mensajes cercanos; el segundo sirve para publicar o dejar de publicar mensajes. Para esta app, queremos que se detengan la publicación y el descubrimiento después de 120 segundos arbitrarios. Para ello, analizaremos un poco más la API, crearemos un objeto PublishOptions
y un objeto SubscribeOptions
, y usamos sus devoluciones de llamada a onExpired()
para desactivar los interruptores de IU de publicación y suscripción.
2. Cómo crear un proyecto de Android Studio
- Inicia un proyecto nuevo de Android Studio.
- Selecciona Empty Activity.
- Asígnale el nombre Nearby Messages Example al proyecto y elige Kotlin como el lenguaje.
3. Código de configuración
- Agrega la versión más reciente de la dependencia de Nearby al archivo
build.gradle
del nivel de la app. De esta manera, puedes usar la API de Nearby Messages para enviar y detectar mensajes de dispositivos cercanos.
implementation 'com.google.android.gms:play-services-nearby:18.0.0'
- Establece la opción de compilación viewBinding en
true
, en el bloque de Android para habilitar ViewBinding.
android {
...
buildFeatures {
viewBinding true
}
}
- Haz clic en Sync Now o en el botón de martillo verde para permitir que Android Studio registre estos cambios de Gradle.
- Agrega los botones de activación "Discover nearby devices" y "Share device information" y la clase RecyclerView que contendrá la lista de dispositivos. En el archivo
activity_main.xml
, reemplaza el código por lo siguiente:
<?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>
Nota: En tu propio proyecto, cambia los valores como 16dp
a recursos como @dimen/activity_vertical_margin
.
4. Cómo agregar Nearby Messages a tu app
Cómo definir variables
- Dentro de la actividad principal (
MainActivity.kt
), sobre la funciónonCreate()
, pega el siguiente fragmento de código para definir las variables a continuación.
/**
* 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 un elemento Strategy
porque queremos personalizar la duración de una transmisión. Para este codelab, elegimos 120 segundos. Si no especificas una estrategia, la API usa los valores predeterminados. Además, aunque en este codelab usamos la misma Strategy
para la publicación y la suscripción, no es obligatorio que tú también lo hagas.
- Cambia la función
onCreate()
para pasar el objeto ViewBinding asetContentView()
. De esta manera, se muestra el contenido del archivo de diseñoactivity_main.xml
.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
Botones de IU de Wire
Esta app realizará tres acciones: publicar mensajes con solo presionar un botón, descubrir mensajes con solo presionar un botón y mostrarlos en una RecyclerView
.
- Queremos que el usuario publique mensajes y anule su publicación, y que descubra mensajes (es decir, se suscriba a ellos). Por ahora, crea métodos de stub para esto llamados
publish()
,unpublish()
,subscribe()
yunsubscribe()
. Crearemos la implementación en un paso posterior.
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")
}
- El usuario puede publicar o descubrir mensajes (es decir, suscribirse a ellos) mediante los botones que se agregaron al diseño de la actividad. Une los dos
Switches
para llamar a los métodos que definimos al final de la funciónonCreate()
.
binding.subscribeSwitch.setOnCheckedChangeListener { buttonView, isChecked ->
if (isChecked) {
subscribe()
} else {
unsubscribe()
}
}
binding.publishSwitch.setOnCheckedChangeListener { buttonView, isChecked ->
if (isChecked) {
publish()
} else {
unpublish()
}
}
- Ahora que agregaste el código de IU para publicar y descubrir mensajes, configura
RecyclerView
a fin de mostrar y quitar mensajes.RecyclerView
mostrará los mensajes que se publican de forma activa. El suscriptor estará a la escucha de mensajes. Cuando se encuentre un mensaje, el suscriptor lo agregará aRecyclerView
. Cuando se pierda un mensaje, es decir, cuando el publicador deje de publicarlo, el suscriptor lo quitará deRecyclerView
.
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 }
}
}
}
- Al final de la función
onCreate()
, agrega una llamada a la funciónsetupMessagesDisplay()
.
override fun onCreate(savedInstanceState: Bundle?) {
...
setupMessagesDisplay()
}
Ahora que la IU está configurada, podemos comenzar a publicar mensajes para que descubran otros dispositivos cercanos. La vista en tu app debería ser la siguiente:
Cómo agregar código de publicación y descubrimiento
- Para enviar un mensaje, primero necesitamos un objeto
Message
. Como se trata de una demostración, solo enviamos el modelo del dispositivo. Agrega este código a la funciónonCreate()
para crear el mensaje que se enviará.
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 un
Message
que puedan descubrir otros dispositivos cercanos, solo tienes que llamar aNearby.getMessagesClient(activity).publish(message)
. Sin embargo, te recomendamos ir más allá y crear tu propio objetoPublishOptions
. Esto te permite especificar tu propiaStrategy
personalizada y aprovechar elPublishCallback
, que informa cuando venció un mensaje publicado. En el siguiente código, creamos una opción que permita desactivar el botón por el usuario cuando venza elTTL
publicado. Luego, pasaremos la opción cuando llamemos apublish()
. Actualiza la funciónpublish()
de la siguiente manera:
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)
}
Este código se ejecuta cada vez que el usuario ACTIVA el interruptor de publicación.
- Si bien la publicación requiere un
Message
, la suscripción requiere unMessageListener
. Pero aquí también recomendamos que compiles un objetoSubscribeOptions
, aunque no sea necesario para que funcione la API. Por ejemplo, si compilas tu propiaSubscriptionOption
, podrás especificar durante cuánto tiempo quieres permanecer en el modo de descubrimiento.
Agrega el siguiente código MessageListener
a la función onCreate()
. Cuando se detecta un mensaje, el objeto de escucha lo agrega a RecyclerView
. Cuando se pierde un mensaje, el objeto de escucha lo quita de 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)
}
}
- Técnicamente, un suscriptor no necesita un
TTL
(o puede establecer suTTL
en infinity). En este codelab, queremos detener el descubrimiento después de 120 segundos. En consecuencia, compilaremos nuestro propio elementoSubscribeOptions
y usaremos su devolución de llamada aonExpired()
para desactivarSwitch
de la IU de suscripción. Actualiza tu función de suscripción con 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)
}
- Es importante que los usuarios puedan desactivar el uso compartido de información. Esto significa permitir que los publicadores dejen de publicar y que los suscriptores dejen de suscribirse. Para dejar de publicar, un publicador debe especificar el mensaje que desea dejar de publicar. Por lo tanto, si se están transmitiendo diez mensajes, puedes dejar de publicar uno, de modo que queden nueve.
private fun unpublish() {
Nearby.getMessagesClient(this).unpublish(message)
}
- Si bien una app puede publicar varios mensajes al mismo tiempo, solo puede tener un
MessageListener
a la vez. Por lo tanto, anular la suscripción es un acción más general. Para detener la suscripción, un suscriptor debe especificar el objeto de escucha.
private fun unsubscribe() {
Nearby.getMessagesClient(this).unsubscribe(messageListener)
}
- Como nota al margen, aunque la API debe detener sus procesos cuando se cierra el proceso del cliente, es posible que quieras dejar de suscribirte (y publicar si es conveniente) en el método del ciclo de vida de
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)
}
En este punto, nuestro código debería compilarse. Sin embargo, la app no funcionará como se espera debido a que falta la clave de API. Tu MainActivity
debe tener la siguiente apariencia:
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 }
}
}
}
}
Cómo agregar una clave de API de Google a tu archivo de manifiesto
La API de Nearby Messages tiene un componente de servidor que Google te proporciona. Cuando publicas un mensaje, la API de Nearby Messages envía el mensaje al servidor de Google, donde un suscriptor puede buscarlo. Para que Google reconozca tu app, debes agregar la API_KEY
de Google al archivo Manifest.xml
. Después de eso, podemos poner manos a la obra y jugar con nuestra app.
Obtener la clave de API es un proceso de tres pasos:
- Ve a Google Developers Console.
- Haz clic en + Create Credentials y elige API Key*.*
- Copia la clave de API creada y pégala en el archivo de manifiesto del proyecto de Android.
Agrega el elemento de metadatos com.google.android.nearby.messages.API_KEY
dentro de la aplicación, en el archivo de manifiesto. El archivo debería parecerse a lo siguiente:
<?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>
Una vez que hayas agregado la clave de API, ejecuta la app en dos o más dispositivos para ver cómo se comunican entre sí.
5. Prácticas recomendadas para la duración de batería
- A fin de proteger la privacidad del usuario y preservar la duración de batería, debes detener la publicación y la suscripción en cuanto el usuario salga de la función cuando sea necesaria.
- Debes usar Nearby Messages para establecer la proximidad entre dispositivos, pero no para la comunicación continua. La comunicación continua puede agotar las baterías del dispositivo a un ritmo de 2.5 a 3.5 veces mayor que el consumo normal.
6. Felicitaciones
¡Felicitaciones! Ahora sabes cómo enviar y descubrir mensajes entre dispositivos cercanos con la API de Nearby Messages.
En resumen, a fin de usar la API de Nearby Messages, debes agregar una dependencia para play-services-nearby
, además de obtener una clave de API en Google Developers Console y agregarla a tu archivo Manifest.xml
. La API requiere conexión a Internet para que los publicadores puedan enviar los mensajes al servidor de Google que sus suscriptores recibirán.
- Aprendiste a enviar mensajes
- Aprendiste a suscribirte para descubrir mensajes
- Aprendiste a usar los mensajes (en este caso, simplemente los mostraste en una
RecyclerView
).
¿Qué sigue?
Consulta la serie de blogs y ejemplos