Ir al contenido

El SDK Flutter de Hooky

La conversación de Hooky en tu app Flutter: una API en Dart y un widget de burbuja, construidos sobre los SDK nativos de Hooky. La conversación en sí es la de los SDK de iOS (SwiftUI) y Android (Jetpack Compose): nativa, sin WebView, con los colores de tu web.

Una app Flutter en Android con el widget HookyBubble abajo a la derecha. La conversación de Hooky abierta desde una app Flutter en un iPhone, en modo oscuro.
El widget HookyBubble en Android, y la conversación nativa en iPhone, en modo oscuro.

Requisitos

  • Flutter 3.35 o posterior (Dart 3.9).
  • Android 7.0 o posterior (minSdk 24, el mínimo del propio Flutter), compilado con compileSdk 36.
  • iOS 15 o posterior.
  • Solo Android e iOS: en la web y en escritorio, las llamadas no hacen nada.
  • Una cuenta de Hooky y un sitio: su clave pública está en Sitios › Instalar, la misma que el data-site-key de la burbuja web.

Instalación

El paquete se instala desde su repositorio de GitHub, como dependencia git. En tu pubspec.yaml:

dependencies:
  hooky_sdk:
    git:
      url: https://github.com/Defdjamel/hooky-flutter
      ref: 1.0.0

y luego flutter pub get. Los SDK nativos de iOS y Android vienen incluidos en el paquete: nada más que añadir, ningún otro repositorio.

iOS

  • El plugin funciona con Swift Package Manager (activado por defecto en las versiones recientes de Flutter) y también con CocoaPods: compila el SDK de iOS que incluye, con su manifiesto de privacidad.
  • Sube el target de despliegue a iOS 15.0: en Xcode, target Runner › General › Minimum Deployments.
  • Para ofrecer la cámara, añade NSCameraUsageDescription a ios/Runner/Info.plist. Sin ella, la opción simplemente se oculta.

Android

No hay nada que declarar: el SDK de Android viene incluido, con su pantalla de conversación, su FileProvider y el permiso INTERNET. Sus dependencias (AndroidX, Compose, OkHttp) vienen de google() y mavenCentral(), ya presentes en los proyectos Flutter.

El código fuente tiene licencia MIT: github.com/Defdjamel/hooky-flutter.

Empezar en 3 líneas

import 'package:hooky_sdk/hooky_sdk.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  Hooky.configure('TU_CLAVE_PUBLICA');                     // 1. al arrancar
  runApp(const MiApp());
}

Scaffold(
  floatingActionButton: const HookyBubble(),      // 2. la burbuja flotante
  …
);

FilledButton(onPressed: Hooky.open, child: const Text('Escríbenos'));   // 3. o tu propio botón

configure carga el nombre, el color y el mensaje de bienvenida del sitio, recupera la conversación guardada y mantiene abierta la conexión en vivo mientras la app está en primer plano. Sus parámetros con nombre: translation: false quita la traducción de los mensajes del equipo (mira Traducción de los mensajes), y apiUrl solo sirve para hacer pruebas contra tu propio servidor.

Todas las llamadas devuelven un Future y nunca lanzan un error: los fallos solo se escriben en la consola, en debug. Antes de configure, identify, setAttributes y track esperan, mientras que open, close y reset no hacen nada.

La burbuja

HookyBubble se parece a la burbuja nativa de cada plataforma: un cuadrado redondeado con sombras suaves en iOS (60 puntos), la tarjeta con borde y sombra marcada de la burbuja web en Android (64 dp). Toma el color del sitio, lleva un indicador para los no leídos y aparece en cuanto se ha llamado a configure.

// Como botón flotante del Scaffold
Scaffold(floatingActionButton: const HookyBubble(), …)

// En cualquier sitio, dentro de un Stack
Stack(children: [
  const MiPantalla(),
  const Positioned(right: 20, bottom: 20, child: SafeArea(child: HookyBubble())),
])

// Tu propia acción, otro tamaño, un estilo impuesto
HookyBubble(onPressed: () => analytics.log('chat'), size: 52, style: HookyBubbleStyle.material)

Abrir y cerrar la conversación

Hooky.open();    // a pantalla completa: una hoja en iOS, una Activity en Android
Hooky.close();   // la cierra

La conversación la dibuja el SDK nativo: las páginas de iOS y Android describen lo que hace (fotos, PDF, borrador, errores).

Mensajes no leídos

La burbuja ya lleva su indicador. Para tus propios botones, Hooky.unreadCount es a la vez un ValueListenable<int> y un Stream<int> (el valor actual y luego cada cambio). Vuelve a 0 cuando se abre la conversación.

// Un indicador en una pestaña
ValueListenableBuilder<int>(
  valueListenable: Hooky.unreadCount,
  builder: (context, n, _) => Badge(isLabelVisible: n > 0, label: Text('$n'), child: const Icon(Icons.chat)),
)

// O como flujo
Hooky.unreadCount.listen((n) => print('$n sin leer'));
final ahora = Hooky.unreadCount.value;

Hooky.state (un ValueListenable<HookyState>) también da isConfigured, siteName, siteColor y sitePosition, para tu propia interfaz. Las respuestas llegan en vivo mientras la app está en primer plano; la versión 1.0.0 no envía notificaciones push a tus usuarios.

Identificar al usuario

Tu app sabe quién ha iniciado sesión: díselo a Hooky. En la ficha de la conversación, tu equipo ve el nombre, el e-mail, tu propio identificador del usuario, el dispositivo y los datos que añadas. Con un e-mail, la conversación ya no pide ni nombre ni e-mail.

// Después de iniciar sesión
Hooky.identify(
  name: user.nombre,
  email: user.email,
  userId: user.id,                                     // tu propio identificador
  attributes: {'plan': 'Pro', 'pedidos': 3, 'newsletter': true},
);

// Más tarde: se combinan con los anteriores
Hooky.setAttributes({'carrito': 49.9, 'cupon': null}); // null borra una clave

// Al cerrar sesión
Hooky.reset();

Cómo funciona

  • Si aún no hay conversación, todo se queda en memoria (incluso antes de configure) y se envía con el primer mensaje. Después, cada cambio se envía al momento, agrupado (un solo envío para todo lo que cambia en el mismo segundo). Sin conexión, se vuelve a enviar más tarde.
  • name y email sustituyen a los de la conversación (null los borra en el dispositivo); userId: null lo deja como está.
  • Un userId distinto del de la conversación significa otra cuenta en el mismo dispositivo: la conversación se olvida, como con reset(). El nuevo usuario no ve el hilo del anterior.
  • Hooky.reset() cierra la conversación y olvida el hilo y la identidad. Llámalo al cerrar sesión.

Reglas de los datos

CampoRegla
Datos libresUn Map plano: 50 claves como máximo, 8 KB en total. Las claves nuevas sustituyen a las anteriores; las demás se mantienen.
ClaveDe 1 a 64 caracteres: letras, cifras, espacios, _, . y -. Si no, se ignora.
ValorUn String (cortado a 500 caracteres), un num o un bool. null borra la clave. Lo demás se descarta, con un aviso en debug.
userId200 caracteres como máximo.
name, emailEl nombre: 100 caracteres como máximo. El e-mail: una dirección válida; si no, se ignora.

Hooky recorta lo que se pasa, sin rechazar nunca el mensaje de tu usuario.

Estos datos son declarativos: la clave del sitio es pública, así que cualquiera puede usarla para enviar el nombre, el e-mail o el userId que quiera. Ayudan a tu equipo a situar a la persona, no demuestran quién es. Nunca pongas en ellos un secreto (contraseña, token, número de tarjeta) ni los uses para dar acceso o actuar sobre una cuenta: compruébalo siempre por tu lado.

Estadísticas

Cuenta lo que pasa en tu app, como Hooky.track en la web: las tarjetas aparecen en Estadísticas, junto a las del sitio. Llámalo justo después de que la acción salga bien.

Hooky.track('registro');
Hooky.track('pedido', value: 49.9, properties: {'plan': 'pro', 'plazas': 3, 'prueba': false});
  • value: un importe o una cantidad, sumado en la tarjeta. Las propiedades son planas (String, num, bool).
  • Si se llama antes de configure, el evento espera (100 como máximo). Los fallos son silenciosos; sin conexión, el evento se pierde.
  • Las mismas reglas que en la web (nombre, 20 propiedades como máximo, límite): mira Estadísticas en la página.

Nunca envíes datos personales (e-mail, nombre, teléfono) en un evento. Cuenta acciones, no personas.

Traducción de los mensajes

Tu usuario y tu equipo escriben cada uno en su idioma, como con la burbuja web (mira Traducción de los mensajes).

  • Debajo de un mensaje del equipo escrito en otro idioma que el de la conversación (el del dispositivo), aparece un enlace discreto Traducir. Una vez traducido, el mensaje se muestra en el idioma del usuario, con «Traducido del inglés · Ver el original» para volver al texto original.
  • El botón de traducción de la cabecera ofrece Traducir siempre: los mensajes del equipo llegan entonces ya traducidos. La elección se guarda en el dispositivo, para cada clave de sitio.
  • Si una traducción falla, se muestra un breve error con Reintentar. Los mensajes del usuario nunca se traducen en su pantalla; tu equipo tiene su propio botón Traducir, en el panel y en la app de Hooky.
  • Solo se traduce el texto, no los archivos adjuntos. La traducción está activada por defecto; para quitarla de tu app: Hooky.configure('TU_CLAVE_PUBLICA', translation: false).

Lo que envía el SDK

Lo mismo que envía la burbuja web, y nada más: los mensajes y archivos del usuario, el nombre, el e-mail, el identificador y los datos que des, el idioma y la zona horaria del dispositivo, y su modelo con la versión del sistema («iPhone 17 Pro · iOS 27.0», «Pixel 8 · Android 15»). Sin identificador publicitario ni seguimiento.

  • Cada petición lleva X-Hooky-Client: hooky-flutter/1.0.0 y X-Hooky-App: <bundle id o package> <version> (<build>): tu equipo ve que la conversación viene de «Flutter · com.example.app 1.0 (1)».
  • El token de la conversación lo guarda el SDK nativo: en el Llavero en iOS (solo en este dispositivo), en un archivo privado nunca copiado en la nube en Android.
  • El plugin incluye un manifiesto de privacidad de iOS (PrivacyInfo.xcprivacy) que no declara ningún rastreo.
  • Para tus fichas de las tiendas, los datos son los de los SDK nativos: mira la ficha del App Store (página de iOS) y la sección Seguridad de los datos de Google Play (página de Android).

Idioma, modo oscuro, accesibilidad

Idioma
La interfaz existe en francés, inglés, español, portugués, italiano y alemán. Sigue el idioma del dispositivo, y en inglés por defecto. El idioma se envía con el primer mensaje, para que tu equipo responda en el idioma adecuado y a la hora adecuada.
Modo oscuro
La conversación y la burbuja siguen el modo claro u oscuro del dispositivo, con el color de tu web.
Accesibilidad
La burbuja es un botón para los lectores de pantalla («Abrir el chat con …», y el número de no leídos); la conversación nativa mantiene VoiceOver y Dynamic Type en iOS, y TalkBack y el tamaño del texto en Android.

R8 y ProGuard

No hay nada que añadir en Android: el SDK no usa reflexión e incluye sus propias reglas.

Solución de problemas

Mi web ha declarado sus direcciones: ¿se rechaza la app?
No. Una app no tiene dirección: se presenta con su cabecera X-Hooky-Client y funciona aunque el sitio haya declarado sus dominios. No añadas nada en Sitios.
«the Hooky plugin is not available on this platform»
Después de añadir el paquete, reinicia la app por completo (flutter run): una recarga en caliente no basta. En la web y en escritorio, el plugin no existe.
La compilación de iOS falla
Revisa el target de despliegue (iOS 15.0): Swift Package Manager y CocoaPods rechazan uno más bajo. Tras cambiar la versión del paquete, ejecuta flutter clean y luego flutter pub get.
La burbuja se queda con los colores por defecto
Revisa la clave pública del sitio (no la clave secreta hks_…, ni una clave API hka_…) y la red.
Sin conexión
El mensaje muestra el error y se queda en el campo de texto; identify y setAttributes se vuelven a enviar más tarde. Los eventos track enviados sin conexión se pierden.

Historial de versiones

1.0.0
Primera versión: Hooky.configure, identify (con userId y datos libres), setAttributes, open, close, track, reset, unreadCount y state en vivo, el widget HookyBubble, la traducción de los mensajes del equipo (translation), sobre los SDK nativos iOS 1.0.0 y Android 1.0.0.