Le SDK Flutter de Hooky
La conversation Hooky dans ton app Flutter : une API Dart et un widget de bulle, posés sur les SDK natifs de Hooky. La conversation elle-même est celle des SDK iOS (SwiftUI) et Android (Jetpack Compose) : native, sans WebView, aux couleurs de ton site.
HookyBubble sur Android, et la conversation native sur iPhone, en mode sombre.Prérequis
- Flutter 3.35 ou plus (Dart 3.9).
- Android 7.0 ou plus (
minSdk 24, le minimum de Flutter lui-même), compilé aveccompileSdk 36. - iOS 15 ou plus.
- Android et iOS seulement : sur le web et le bureau, les appels ne font rien.
- Un compte Hooky et un site : sa clé publique est dans Sites › Installer, la même que le
data-site-keyde la bulle web.
Installation
Le paquet s’installe depuis son dépôt GitHub, en dépendance git. Dans ton pubspec.yaml :
dependencies:
hooky_sdk:
git:
url: https://github.com/Defdjamel/hooky-flutter
ref: 1.0.0puis flutter pub get. Les SDK natifs iOS et Android sont inclus dans le paquet : rien d’autre à ajouter, aucun autre dépôt.
iOS
- Le plugin marche avec Swift Package Manager (actif par défaut dans les versions récentes de Flutter) comme avec CocoaPods : il compile le SDK iOS qu’il embarque, avec son manifeste de confidentialité.
- Monte la cible de déploiement à iOS 15.0 : dans Xcode, cible Runner › General › Minimum Deployments.
- Pour proposer l’appareil photo, ajoute
NSCameraUsageDescriptionàios/Runner/Info.plist. Sans elle, l’option est simplement cachée.
Android
Rien à déclarer : le SDK Android est inclus, avec son écran de conversation, son FileProvider et l’autorisation INTERNET. Ses dépendances (AndroidX, Compose, OkHttp) viennent de google() et mavenCentral(), déjà dans les projets Flutter.
Le code source est sous licence MIT : github.com/Defdjamel/hooky-flutter.
Démarrer en 3 lignes
import 'package:hooky_sdk/hooky_sdk.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
Hooky.configure('TA_CLE_PUBLIQUE'); // 1. au lancement
runApp(const MonApp());
}
Scaffold(
floatingActionButton: const HookyBubble(), // 2. la bulle flottante
…
);
FilledButton(onPressed: Hooky.open, child: const Text('Nous écrire')); // 3. ou ton propre boutonconfigure 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. Ses paramètres nommés : translation: false retire la traduction des messages de l’équipe (voir Traduction des messages), apiUrl ne sert qu’à tester contre ton propre serveur.
Tous les appels renvoient un Future et ne lèvent jamais d’erreur : les échecs ne sont écrits que dans la console, en debug. Avant configure, identify, setAttributes et track attendent, tandis qu’open, close et reset ne font rien.
La bulle
HookyBubble ressemble à la bulle native de chaque plateforme : un carré arrondi aux ombres douces sur iOS (60 points), la carte cernée à l’ombre franche de la bulle web sur Android (64 dp). Elle prend la couleur du site, porte une pastille pour les non-lus et apparaît dès que configure a été appelé.
// En bouton flottant du Scaffold
Scaffold(floatingActionButton: const HookyBubble(), …)
// N’importe où, dans une Stack
Stack(children: [
const MonEcran(),
const Positioned(right: 20, bottom: 20, child: SafeArea(child: HookyBubble())),
])
// Ta propre action, une autre taille, un style imposé
HookyBubble(onPressed: () => analytics.log('chat'), size: 52, style: HookyBubbleStyle.material)Ouvrir et fermer la conversation
Hooky.open(); // plein écran : une feuille sur iOS, une Activity sur Android
Hooky.close(); // la refermeLa conversation est dessinée par le SDK natif : les pages iOS et Android décrivent ce qu’elle fait (photos, PDF, brouillon, erreurs).
Messages non lus
La bulle porte déjà sa pastille. Pour tes propres boutons, Hooky.unreadCount est à la fois un ValueListenable<int> et un Stream<int> (la valeur actuelle, puis chaque changement). Il retombe à 0 quand la conversation s’ouvre.
// Une pastille sur un onglet
ValueListenableBuilder<int>(
valueListenable: Hooky.unreadCount,
builder: (context, n, _) => Badge(isLabelVisible: n > 0, label: Text('$n'), child: const Icon(Icons.chat)),
)
// Ou en flux
Hooky.unreadCount.listen((n) => print('$n non lus'));
final maintenant = Hooky.unreadCount.value;Hooky.state (un ValueListenable<HookyState>) donne aussi isConfigured, siteName, siteColor et sitePosition, pour ta propre interface. Les réponses arrivent en direct tant que l’app est au premier plan ; la version 1.0.0 n’envoie pas de notification push à tes utilisateurs.
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: user.prenom,
email: user.email,
userId: user.id, // ton identifiant à toi
attributes: {'offre': 'Pro', 'commandes': 3, 'newsletter': true},
);
// Plus tard : fusionnées avec les précédentes
Hooky.setAttributes({'panier': 49.9, 'coupon': null}); // null efface une clé
// À la déconnexion
Hooky.reset();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 plus tard. nameetemailremplacent ceux de la conversation (nullles efface sur l’appareil) ;userId: nullle laisse tel quel.- Un
userIddifférent de celui de la conversation, c’est un autre compte sur le même appareil : la conversation est oubliée, comme avecreset(). Le nouvel utilisateur ne voit pas le fil du précédent. Hooky.reset()referme la conversation et oublie le fil et l’identité. Appelle-le à la déconnexion.
Les règles des données
| Champ | Règle |
|---|---|
| Données libres | Une 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. |
| Valeur | Un String (coupé à 500 caractères), un num ou un bool. null efface la clé. Le reste est écarté, avec un avertissement en debug. |
userId | 200 caractères au plus. |
name, email | Le 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', value: 49.9, properties: {'offre': 'pro', 'places': 3, 'essai': false});value: un montant ou une quantité, additionné dans la carte. Les propriétés sont à plat (String,num,bool).- Appelé avant
configure, l’événement attend (100 au plus). 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.
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('TA_CLE_PUBLIQUE', translation: false).
Ce que le SDK envoie
Ce qu’envoie la bulle web, et rien d’autre : les messages et fichiers de l’utilisateur, le nom, l’e-mail, l’identifiant et les données que tu donnes, la langue et le fuseau horaire de l’appareil, et son modèle avec la version du système (« iPhone 17 Pro · iOS 27.0 », « Pixel 8 · Android 15 »). Sans identifiant publicitaire ni suivi.
- Chaque requête porte
X-Hooky-Client: hooky-flutter/1.0.0etX-Hooky-App: <bundle id ou package> <version> (<build>): ton équipe voit la conversation venir de « Flutter · com.exemple.app 1.0 (1) ». - Le jeton de la conversation est gardé par le SDK natif : dans le Trousseau sur iOS (cet appareil seulement), dans un fichier privé jamais sauvegardé sur Android.
- Le plugin embarque un manifeste de confidentialité iOS (
PrivacyInfo.xcprivacy) qui ne déclare aucun suivi. - Pour tes fiches de store, les données sont celles des SDK natifs : voir la fiche App Store (page iOS) et la section Sécurité des données de Google Play (page Android).
Langue, mode sombre, accessibilité
- Langue
- L’interface existe en français, anglais, espagnol, portugais, italien et allemand. Elle suit la langue de l’appareil, 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.
- Mode sombre
- La conversation et la bulle suivent le mode clair ou sombre de l’appareil, avec la couleur de ton site.
- Accessibilité
- La bulle est un bouton pour les lecteurs d’écran (« Ouvrir le chat avec … », et le nombre de non-lus) ; la conversation native garde VoiceOver et Dynamic Type sur iOS, TalkBack et la taille du texte sur Android.
R8 et ProGuard
Rien à ajouter sur Android : le SDK n’utilise pas la réflexion et embarque ses propres règles.
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-Clientet passe même quand le site a déclaré ses domaines. N’ajoute rien dans Sites. - « the Hooky plugin is not available on this platform »
- Après l’ajout du paquet, relance complètement l’app (
flutter run), un rechargement à chaud ne suffit pas. Sur le web et le bureau, le plugin n’existe pas. - La compilation iOS échoue
- Vérifie la cible de déploiement (iOS 15.0) : Swift Package Manager et CocoaPods refusent une cible plus basse. Après un changement de version du paquet, lance
flutter cleanpuisflutter pub get. - La bulle reste aux couleurs par défaut
- Vérifie la clé publique du site (pas la clé secrète
hks_…, ni une clé APIhka_…), et le réseau. - Hors ligne
- Le message affiche l’erreur et reste dans le champ ;
identifyetsetAttributesrepartent plus tard. Les événementstrackenvoyés hors ligne sont perdus.
Journal des versions
- 1.0.0
- Première version :
Hooky.configure,identify(avecuserIdet données libres),setAttributes,open,close,track,reset,unreadCountetstateen direct, le widgetHookyBubble, la traduction des messages de l’équipe (translation), sur les SDK natifs iOS 1.0.0 et Android 1.0.0.