Aller au contenu

Le SDK Android de Hooky

La conversation Hooky, native dans ton app Android : écrite avec Jetpack Compose (Material 3), sans WebView, aux couleurs de ton site. Elle marche dans une app Compose comme dans une app à vues XML, en Kotlin comme en Java.

Un écran d’app Android avec la bulle Hooky en bas à droite et un non-lu. La conversation Hooky sur Android : une photo, un PDF reçu et un lien.
La bulle avec sa pastille de non-lus, et la conversation avec une photo et un PDF.

Prérequis

  • Android 6.0 ou plus (minSdk 23).
  • Le SDK est compilé avec compileSdk 36 (Android 16) : compile ton app avec la même version ou une plus récente.
  • Kotlin ou Java. Ton app n’a pas besoin d’utiliser Compose : le SDK l’apporte pour son propre écran.
  • Dépendances : AndroidX (Activity, Core, Compose), les coroutines Kotlin et OkHttp. Pas de bibliothèque d’images, de JSON ni d’analyse.
  • Un compte Hooky et un site : sa clé publique est dans Sites › Installer, la même que le data-site-key de la bulle web.

Installation

Le SDK est servi depuis son dépôt GitHub, sous forme de dépôt Maven (GitHub Pages). Ajoute ce dépôt à ton projet, dans settings.gradle.kts :

// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven("https://defdjamel.github.io/hooky-android")
    }
}

puis la dépendance au module de ton app :

// app/build.gradle.kts
dependencies {
    implementation("com.heyhooky:hooky-android:1.0.0")
}

En Groovy : maven { url 'https://defdjamel.github.io/hooky-android' } et implementation 'com.heyhooky:hooky-android:1.0.0'. Les dépendances du SDK (AndroidX, Compose, OkHttp, coroutines) viennent de google() et mavenCentral(). Le code source est sous licence MIT : github.com/Defdjamel/hooky-android.

Démarrer en 3 lignes

// 1. Une fois, au lancement (Application.onCreate est le meilleur endroit)
Hooky.configure(context, "TA_CLE_PUBLIQUE")

// 2. La bulle toute faite, dans un coin de ton écran (Compose)
HookyBubble(Modifier.align(Alignment.BottomEnd).navigationBarsPadding().padding(20.dp))

// 3. Ou la conversation depuis ton propre bouton
Hooky.open(context)

configure charge le nom, la couleur et le message d’accueil du site, reprend la conversation enregistrée et garde la connexion en direct ouverte tant que l’app est au premier plan.

class MonApp : Application() {
    override fun onCreate() {
        super.onCreate()
        Hooky.configure(this, "TA_CLE_PUBLIQUE")
    }
}
// AndroidManifest.xml : <application android:name=".MonApp" …>

Tous les appels se font sur le fil principal et ne lèvent jamais d’erreur. configure prend aussi translation = false, pour retirer la traduction des messages de l’équipe (voir Traduction des messages), et apiUrl, qui ne sert qu’à tester contre ton propre serveur.

La bulle

Un bouton à la couleur de ton site (64 dp), avec le logo Hooky et une pastille pour les réponses pas encore lues. Le toucher ouvre la conversation. Il apparaît dès que l’apparence du site est chargée.

Jetpack Compose

Box(Modifier.fillMaxSize()) {
    MonEcran()
    HookyBubble(Modifier.align(Alignment.BottomEnd).navigationBarsPadding().padding(20.dp))
}

// Avec ta propre action
HookyBubble(onClick = { afficherAide = true })

Vues XML

<FrameLayout …>
    <!-- ton écran -->
    <com.heyhooky.sdk.HookyBubbleView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="20dp" />
</FrameLayout>

L’activité qui l’affiche doit être une AppCompatActivity ou une ComponentActivity.

Ouvrir et fermer la conversation

Hooky.open(context)   // plein écran (une Activity), depuis n’importe quel Context
Hooky.close()         // la referme

L’écran de conversation, HookyActivity, est déclaré par le SDK : tu n’as rien à ajouter à ton manifeste.

Messages non lus

La bulle porte déjà sa pastille. Pour tes propres boutons, le nombre de réponses de l’équipe pas encore vues se suit en direct ; il retombe à 0 quand la conversation s’ouvre.

// Compose : un StateFlow<Int>
val nonLus by Hooky.unreadCount.collectAsState()
BadgedBox(badge = { if (nonLus > 0) Badge { Text("$nonLus") } }) { Icon(Icons.Default.Email, null) }

// Vues ou Java : un écouteur (la valeur actuelle, puis chaque changement)
val ecoute: Closeable = Hooky.addUnreadCountListener { n -> badge.isVisible = n > 0 }
ecoute.close()   // pour arrêter

En Java :

Hooky.configure(this, "TA_CLE_PUBLIQUE");
Closeable ecoute = Hooky.addUnreadCountListener(n -> badge.setText(String.valueOf(n)));

Les réponses arrivent en direct tant que l’app est au premier plan ; au retour, le SDK rattrape ce qu’il a manqué. La version 1.0.0 n’envoie pas de notification push à tes utilisateurs. Hooky.isConfigured et Hooky.VERSION complètent l’API.

Identifier l’utilisateur

Ton app sait qui est connecté : dis-le à Hooky. Ton équipe lit, dans la fiche de la conversation, le nom, l’e-mail, ton identifiant de l’utilisateur, l’appareil et les données que tu y joins. Avec un e-mail, la conversation ne demande plus ni prénom ni e-mail.

// Après la connexion
Hooky.identify(
    name = "Lou",
    email = "lou@exemple.fr",
    userId = "u_1042",                                  // ton identifiant à toi
    attributes = mapOf("offre" to "Pro", "panier" to 49.9, "beta" to true),
)

// Plus tard : fusionnées avec les précédentes
Hooky.setAttributes(mapOf("panier" to 12, "dernier_ecran" to "Paiement"))
Hooky.setAttributes(mapOf("beta" to null))             // null efface une clé

// À la déconnexion
Hooky.reset()

En Java : Hooky.identify("Lou", "lou@exemple.fr", "u_1042");, ou avec une Map<String, Object> de données en quatrième paramètre.

Comment ça marche

  • Pas encore de conversation : tout reste en mémoire (même avant configure) et part avec le premier message. Ensuite, chaque changement part aussitôt, regroupé (un seul envoi pour tout ce qui change dans la même seconde). Hors ligne, il repart au message suivant ou au retour de l’app au premier plan.
  • name et email remplacent ceux de la conversation (null les efface sur l’appareil) ; userId = null le laisse tel quel.
  • Le userId est gardé avec le jeton de la conversation. Un userId différent, c’est un autre compte sur le même appareil : la conversation est oubliée, comme avec reset(). Le nouvel utilisateur ne voit pas le fil du précédent.
  • Hooky.reset() referme la conversation et oublie le fil, l’identité et les données. Appelle-le à la déconnexion.

Les règles des données

ChampRègle
Données libresUne Map à plat : 50 clés au plus, 8 Ko en tout. Les nouvelles clés remplacent les anciennes, les autres restent.
Clé1 à 64 caractères : lettres, chiffres, espaces, _, . et -. Sinon, elle est ignorée.
ValeurUn texte (coupé à 500 caractères), un nombre ou un booléen. null efface la clé. Les listes et les maps sont ignorées, les autres objets envoyés comme texte.
userId200 caractères au plus.
name, emailLe nom : 100 caractères au plus. L’e-mail : une adresse valide, sinon il est ignoré.

Hooky nettoie ce qui dépasse, sans jamais refuser le message de ton utilisateur.

Ces données sont déclaratives : la clé du site est publique, et n’importe qui peut l’utiliser pour envoyer un nom, un e-mail ou un userId de son choix. Elles aident ton équipe à situer la personne, elles ne prouvent pas qui elle est. N’y mets jamais de secret (mot de passe, jeton, numéro de carte) et ne t’en sers jamais pour donner un accès ou agir sur un compte : vérifie toujours de ton côté.

Stats

Compte ce qui se passe dans ton app, comme Hooky.track sur le web : les cartes s’affichent dans Stats, avec celles du site. Appelle-le juste après que l’action a réussi.

Hooky.track("inscription")
Hooky.track("commande", 49.9, mapOf("offre" to "pro", "places" to 3, "essai" to false))
  • Le deuxième paramètre, value : un montant ou une quantité, additionné dans la carte. Les propriétés sont à plat (textes, nombres, booléens).
  • Appelle configure d’abord : avant, track ne fait rien. Les échecs sont silencieux ; hors ligne, l’événement est perdu.
  • Mêmes règles que sur le web (nom, 20 propriétés au plus, débit) : voir Stats dans la page.

N’envoie jamais de donnée personnelle (e-mail, nom, téléphone) dans un événement. Compte des actions, pas des personnes.

Ce que fait la conversation

  • Le premier message, avec prénom et e-mail facultatifs (ou ceux d’identify), puis un fil comme celui de la bulle web : séparateurs de jours, noms de l’équipe, liens, emoji en grand, « Lu », « … écrit », messages plus anciens à la demande.
  • Jusqu’à 4 pièces jointes par message, 10 Mo chacune, avec une barre de progression : photos du sélecteur du système (aucune autorisation à demander), de l’appareil photo (par l’app photo du téléphone) et PDF de Fichiers.
  • Les photos reçues s’ouvrent en plein écran (pincer ou toucher deux fois pour zoomer) ; les fichiers dans l’app qui sait les lire, ou se partagent.
  • Le brouillon survit à la fermeture de l’écran et de l’app. Le clavier ne couvre jamais le champ de saisie.
  • Des erreurs en clair : hors ligne, trop de messages d’un coup, fichier trop lourd ou refusé.

Ce que le SDK ajoute à ton app

  • L’autorisation INTERNET, et aucune autre.
  • HookyActivity et un FileProvider (autorité <ton.package>.hooky.fichiers, limité au dossier hooky/ du cache de ton app) pour les photos de l’appareil photo et les fichiers reçus.
  • Des <queries> pour les apps qui prennent des photos ou ouvrent les PDF (Android 11 et plus).

Si ton app déclare elle-même l’autorisation CAMERA, Android l’exige aussi pour ouvrir l’app photo : le SDK la demande alors avant.

Traduction des messages

Ton utilisateur et ton équipe écrivent chacun dans leur langue, comme avec la bulle web (voir Traduction des messages).

  • Sous un message de l’équipe écrit dans une autre langue que celle de la conversation (celle de l’appareil), un lien discret Traduire. Traduit, le message s’affiche dans la langue de l’utilisateur, avec « Traduit de l’anglais · Voir l’original » pour revenir au texte d’origine.
  • Le bouton de traduction de l’en-tête propose Toujours traduire : les messages de l’équipe arrivent alors déjà traduits. Le choix est gardé sur l’appareil, pour chaque clé de site.
  • Une traduction ratée affiche une courte erreur avec Réessayer. Les messages de l’utilisateur ne sont jamais traduits chez lui ; ton équipe a son propre bouton Traduire, dans le tableau de bord et l’app Hooky.
  • Seul le texte est traduit, pas les pièces jointes. La traduction est active par défaut ; pour la retirer de ton app : Hooky.configure(context, "TA_CLE_PUBLIQUE", translation = false).

Ce que le SDK envoie

Ce qu’envoie la bulle web, et rien d’autre. Sans identifiant publicitaire, sans identifiant d’appareil, sans bibliothèque d’analyse. Pour remplir la section Sécurité des données de ta fiche Google Play :

DonnéeQuandSécurité des données
MessagesQuand l’utilisateur écritMessages : autres messages dans l’application
Photos et PDFQuand il les jointPhotos et vidéos : photos ; fichiers et documents
Prénom et e-mailS’il les donne, ou si tu les passes à identifyInformations personnelles : nom, adresse e-mail
Ton userIdSi tu le passes à identifyInformations personnelles : ID utilisateur
Données libresCe que tu passes à identify et setAttributesSelon ce que tu y mets
Événements trackQuand tu les envoies, sans identifiant de personneActivité dans l’application : autres actions (analyses)
Appareil, langue, fuseau horaire, appAvec la conversationInformations techniques pour ton équipe (voir ci-dessous)
  • Ces données servent au fonctionnement de l’app (le support client), et les stats à tes analyses. Elles sont chiffrées en transit (HTTPS et WebSocket sécurisé). Hooky les traite pour ton compte : au sens de Google Play, ce n’est pas un partage avec un tiers.
  • L’appareil est lisible : « Samsung SM-S918B · Android 15 » (fabricant, modèle et version d’Android). La page de la conversation est android-app://<ton.package>. Chaque requête porte X-Hooky-Client: hooky-android/1.0.0 et X-Hooky-App: <package> <versionName> (<versionCode>) : ton équipe voit la conversation venir de « Android · com.exemple.app 2.3.1 (231) ».
  • Sur l’appareil : le jeton de la conversation (la seule clé de l’utilisateur), le userId et le brouillon, par clé de site, dans un fichier privé de ton app sous noBackupFilesDir : jamais sauvegardé dans le cloud ni restauré sur un autre appareil. Une traduction n’envoie que l’identifiant du message et la langue de la conversation.
  • Hooky est sous-traitant au sens du RGPD : voir la politique de confidentialité. Tu restes responsable de ta fiche : déclare ce que ton app envoie vraiment.

Langue, mode sombre, accessibilité

Langue
L’interface existe en français, anglais, espagnol, portugais, italien et allemand. Elle suit la langue de l’appareil au moment de configure, l’anglais par défaut. La langue part avec le premier message, pour que ton équipe réponde dans la bonne langue et à la bonne heure (avec le fuseau horaire).
Mode sombre
La conversation et la bulle suivent le thème clair ou sombre du système, avec la couleur de ton site. Rien à régler.
Accessibilité
Libellés TalkBack sur chaque contrôle (la bulle : « Ouvrir le chat avec … », et le nombre de non-lus), taille du texte du système respectée.

R8 et ProGuard

Rien à ajouter. Le SDK n’utilise pas la réflexion et embarque ses propres règles : R8 peut tout réduire dans ton build de production.

Dépannage

Mon site a déclaré ses adresses : l’app est-elle refusée ?
Non. Une app n’a pas d’adresse : elle se présente par son en-tête X-Hooky-Client et passe même quand le site a déclaré ses domaines. N’ajoute rien dans Sites.
La bulle n’apparaît pas
Elle attend l’apparence du site. Vérifie que configure est appelé, avec la clé publique du site (pas la clé secrète hks_…, ni une clé API hka_…). Sans réseau au lancement, elle apparaît au prochain retour de l’app au premier plan.
Plantage avec HookyBubbleView
L’activité doit être une AppCompatActivity ou une ComponentActivity.
Hors ligne
Le message affiche l’erreur et reste dans le champ ; identify et setAttributes repartent plus tard. Les événements track envoyés hors ligne sont perdus.
Un autre utilisateur voit l’ancienne conversation
Appelle Hooky.reset() à la déconnexion, et passe un userId à identify : un identifiant différent repart d’une conversation vierge.

Journal des versions

1.0.0
Première version : configure, la bulle Compose et XML, open et close, identify (avec userId et données libres), setAttributes, track, reset, les non-lus en direct (StateFlow et écouteur Java), photos et PDF, la traduction des messages de l’équipe, six langues, mode sombre et TalkBack.