Ajouter Nearby Messages à votre appli

1. Avant de commencer

Imaginez cette situation : vous êtes dans un centre commercial bondé et vous apercevez votre amie Jane. Vous serez tenté d'agiter votre main et de l'interpeller pour attirer son attention. L'API Nearby Messages de Google permet à votre application d'attirer l'attention des utilisateurs en agitant la main dans leur direction pour qu'ils puissent facilement se reconnaître lorsqu'ils se trouvent à proximité les uns des autres. Cet atelier de programmation vous explique comment utiliser l'API Nearby Messages pour activer les interactions utilisateur en fonction de la proximité physique. Pour simplifier l'atelier de programmation, chaque utilisateur publiera le modèle de compilation de son téléphone : android.os.Build.MODEL. Vous pouvez demander à chaque utilisateur de publier ses userId (ou d'autres informations adaptées à votre cas d'utilisation) vers l'appareil d'amis à proximité. L'API Nearby Messages allie la connectivité Internet, le Bluetooth et d'autres technologies pour vous offrir cette fonctionnalité.

Conditions préalables

  • Vous disposez de connaissances de base en développement avec Kotlin et Android.
  • Vous savez créer et exécuter des applications dans Android Studio.
  • Vous avez au moins deux appareils Android pour exécuter et tester le code.

Points abordés

  • Comment ajouter la bibliothèque Nearby à votre application
  • Comment diffuser des messages auprès des personnes intéressées
  • Comment détecter les messages provenant de points d'intérêt
  • Comment obtenir une clé API pour vos messages
  • Bonnes pratiques concernant l'autonomie de la batterie

Ce dont vous avez besoin

  • Un compte Google (une adresse Gmail) pour obtenir une clé API Google.
  • La dernière version d'Android Studio.
  • Deux appareils Android sur lesquels les services Google Play (le Play Store) sont installés.
  • Une connexion Internet (contrairement à l'API Nearby Connections, qui n'en a pas besoin).

Objectifs de l'atelier

Créer une application d'Activity qui permet à un utilisateur de publier des informations sur l'appareil et de recevoir des informations au sujet des appareils à proximité. L'application propose deux boutons bascule, que l'utilisateur peut activer ou désactiver : le premier permet de découvrir ou d'arrêter la découverte des messages à proximité. Le deuxième permet de publier des messages ou d'annuler leur publication. Pour cette application, nous voulons que la publication et la découverte s'arrêtent après une durée arbitraire de 120 secondes. C'est pourquoi nous allons nous pencher un peu plus en détail sur l'API et créer un objet PublishOptions et un objet SubscribeOptions, puis utiliser leurs rappels onExpired() pour désactiver les boutons "Publier" et "S'abonner".

56bd91ffed49ec3d.png

2. Créer un projet Android Studio

  1. Lancez un nouveau projet Android Studio.
  2. Sélectionnez Empty Activity (Activité vide).

f2936f15aa940a21.png

  1. Nommez le projet Exemple Nearby Messages et définissez le langage sur Kotlin.

3220c65e598bf6af.png

3. Configurer le code

  1. Ajoutez la dernière version de la dépendance Nearby dans le fichier build.gradle au niveau de l'application. Vous pouvez ainsi utiliser l'API Nearby Messages pour envoyer et détecter des messages à partir d'appareils situés à proximité.
implementation 'com.google.android.gms:play-services-nearby:18.0.0'
  1. Définissez l'option de compilation viewBinding sur true dans le bloc Android pour activer ViewBinding.
android {
   ...
   buildFeatures {
       viewBinding true
   }
}
  1. Cliquez sur Synchroniser ou sur le bouton en forme de marteau vert pour qu'Android Studio tienne compte de ces modifications Gradle.

57995716c771d511.png

  1. Ajoutez les boutons "Détecter les appareils à proximité" et "Partager les informations de l'appareil", ainsi que le RecycleView qui contient la liste des appareils. Dans le fichier activity_main.xml, remplacez le code par ce qui suit :
<?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>

Remarque : Dans votre projet, remplacez des valeurs telles que 16dp par des ressources telles que @dimen/activity_vertical_margin.

4. Ajouter Nearby Messages à votre appli

Définir les variables

  1. Dans l'activité principale (MainActivity.kt), au-dessus de la fonction onCreate(), définissez les variables suivantes en collant cet extrait de code.
/**
* 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

Nous avons défini un paramètre Strategy pour personnaliser la durée d'une diffusion. Pour cet atelier de programmation, nous choisissons 120 secondes. Si vous ne spécifiez pas de stratégie, l'API utilise les valeurs par défaut. De plus, nous utilisons la même Strategy pour la publication et l'abonnement dans cet atelier de programmation, vous n'êtes pas obligé de la suivre.

  1. Modifiez votre fonction onCreate() pour transmettre l'objet ViewBinding à setContentView(). Le contenu du fichier de mise en page activity_main.xml s'affiche.
override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   binding = ActivityMainBinding.inflate(layoutInflater)
   setContentView(binding.root)
}

Raccorder les boutons de l'interface utilisateur

Cette application vous permet d'effectuer trois opérations : publier et découvrir des messages d'un seul clic, et afficher les messages dans une RecyclerView.

  1. Nous souhaitons que l'utilisateur puisse publier et annuler la publication de messages, et qu'il découvre les messages auxquels il s'abonne. Pour l'instant, créez des méthodes bouchons appelées publish(), unpublish(), subscribe() et unsubscribe(). Nous créerons l'implémentation lors d'une prochaine étape.
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")
}
  1. L'utilisateur peut publier ou découvrir (c'est-à-dire s'abonner à) des messages à l'aide des boutons bascule ajoutés à la mise en page de l'activité. Branchez les deux Switches pour appeler les méthodes définies à la fin de la fonction onCreate().
   binding.subscribeSwitch.setOnCheckedChangeListener { buttonView, isChecked ->
       if (isChecked) {
           subscribe()
       } else {
           unsubscribe()
       }
   }

   binding.publishSwitch.setOnCheckedChangeListener { buttonView, isChecked ->
       if (isChecked) {
           publish()
       } else {
           unpublish()
       }
   }
  1. Maintenant que vous avez ajouté le code de l'interface utilisateur pour la publication et la découverte des messages, configurez le RecyclerView pour l'affichage et la suppression des messages. RecyclerView affiche les messages en cours de publication. L'abonné écoutera les messages. Lorsqu'un message est trouvé, l'abonné l'ajoute au RecyclerView. Lorsqu'un message est perdu, c'est-à-dire que l'éditeur arrête sa publication, l'abonné le supprime du RecyclerView.
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 }
       }
   }
}
  1. À la fin de la fonction onCreate(), ajoutez un appel à la fonction setupMessagesDisplay().
override fun onCreate(savedInstanceState: Bundle?) {
   ...
   setupMessagesDisplay()
}

Maintenant que l'interface utilisateur est configurée, nous pouvons commencer à publier des messages qui seront découverts par les autres appareils à proximité. Voici à quoi doit ressembler votre application :

56bd91ffed49ec3d.png

Ajouter un code de publication et de découverte

  1. Pour envoyer un message, nous avons d'abord besoin d'un objet Message. Comme il s'agit d'une démonstration, nous nous contentons d'envoyer le modèle de l'appareil. Ajoutez le code suivant à la fonction onCreate() pour créer le message à envoyer.
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")))

}
  1. Pour publier un Message que d'autres appareils à proximité peuvent détecter, il vous suffit d'appeler Nearby.getMessagesClient(activity).publish(message). Toutefois, nous vous recommandons d'aller plus loin en créant votre propre objet PublishOptions. Ainsi, vous pourrez spécifier une Strategy personnalisée et bénéficier du PublishCallback, qui signale l'expiration d'un message publié. Dans le code suivant, nous créons une option qui nous permet de désactiver le boutons bascule pour l'utilisateur lorsque le TTL publié arrive à expiration. Nous transmettons ensuite l'option lorsque nous appelons publish(). Mettez à jour votre fonction publish() comme suit.
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)
}

Ce code s'exécute chaque fois que l'utilisateur active le bouton publish (publier).

  1. Alors que la publication nécessite un Message, l'abonnement nécessite un MessageListener. Cependant, nous vous recommandons également de créer un objet SubscribeOptions, même s'il n'est pas nécessaire au fonctionnement de l'API. La création de votre propre SubscriptionOption vous permet, par exemple, de spécifier la durée pendant laquelle vous souhaitez rester en mode découverte.

Ajoutez la code MessageListener suivant à la fonction onCreate(). Lorsqu'un message est détecté, l'écouteur l'ajoute à RecyclerView. Lorsqu'un message est perdu, l'écouteur le supprime du 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)
   }
}
  1. Techniquement, un abonné n'a pas besoin de TTL (il peut aussi définir son TTL sur infini). Mais dans cet atelier de programmation, nous souhaitons arrêter la découverte après 120 secondes. Nous allons donc créer notre propre SubscribeOptions et utiliser son rappel onExpired() pour désactiver le Switch d'abonnement dans l'interface utilisateur. Mettez à jour votre fonction d'abonnement avec le code suivant :
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)
}
  1. Il est important de permettre à vos utilisateurs de désactiver le partage d'informations. Cela signifie que les éditeurs peuvent arrêter de publier et que les abonnés peuvent arrêter de s'abonner. Pour arrêter la publication, l'éditeur doit préciser le message qu'il souhaite arrêter. Par conséquent, si vous avez 10 messages publiés, vous pouvez arrêter la publication d'un message, et laisser les neuf autres.
private fun unpublish() {
   Nearby.getMessagesClient(this).unpublish(message)
}
  1. Bien qu'une application puisse publier plusieurs messages en même temps, elle ne peut comporter qu'un seul MessageListener à la fois. Le désabonnement est donc plus général. Pour arrêter de s'abonner, un abonné doit spécifier l'écouteur.
private fun unsubscribe() {
   Nearby.getMessagesClient(this).unsubscribe(messageListener)
}
  1. L'API doit arrêter ses processus lorsque le processus s'arrête côté client, mais vous pouvez également arrêter l'abonnement (et la publication, si possible) dans votre méthode de cycle de vie 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)
}

À ce stade, le code devrait se compiler. Cependant, l'application ne fonctionne pas correctement, car il lui manque la clé API. Votre MainActivity devrait se présenter comme suit :

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

Ajouter une clé API de Google à votre fichier manifeste

L'API Nearby Messages possède un composant de serveur fourni par Google. Lorsque vous publiez un message, l'API Nearby Messages l'envoie au serveur de Google, où un abonné peut l'interroger. Pour que Google reconnaisse votre application, vous devez ajouter l'API_KEY de Google au fichier Manifest.xml. Ensuite, nous pouvons exécuter et utiliser notre application.

L'obtention de la clé API s'effectue en trois étapes :

  1. Accédez à la Google Developers Console.
  2. Cliquez sur Créer des identifiants et sélectionnez Clé API*.*
  3. Copiez la clé API créée et collez-la dans le fichier manifeste de votre projet Android.

Ajoutez l'élément de métadonnées com.google.android.nearby.messages.API_KEY dans l'application, dans votre fichier manifeste. Le fichier doit se présenter comme suit :

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

Une fois la clé API ajoutée, exécutez l'application sur au moins deux appareils pour qu'ils communiquent entre eux.

ba105a7c853704ac.gif

5. Bonnes pratiques concernant l'autonomie de la batterie

  • Pour protéger la vie privée des utilisateurs et préserver l'autonomie de la batterie, vous devez cesser la publication et l'abonnement dès que l'utilisateur quitte la fonctionnalité, le cas échéant.
  • Utilisez la fonctionnalité Nearby Messages pour établir la proximité entre vos appareils, mais pas pour assurer une communication continue. La communication en continu peut décharger la batterie de l'appareil entre 2,5 et 3,5 fois plus vite que la normale.

6. Félicitations

Félicitations ! Vous savez maintenant envoyer et découvrir des messages entre des appareils à proximité à l'aide de l'API Nearby Messages.

En résumé, pour utiliser l'API Nearby Messages, vous devez ajouter la dépendance play-services-nearby. Vous devez également obtenir une clé API à partir de la Google Developers Console et l'ajouter à votre fichier Manifest.xml. L'API requiert une connexion Internet pour que les éditeurs puissent envoyer leurs messages au serveur Google et que les abonnés puissent les récupérer.

  • Vous avez appris à envoyer des messages.
  • Vous avez appris à vous abonner pour découvrir les messages.
  • Vous avez appris à utiliser les messages (dans ce cas, vous les avez affichés dans un RecyclerView).

Et maintenant ?

Consultez notre série d'articles de blog et notre application exemple :