Ir para o conteúdo

O SDK Flutter do Hooky

A conversa do Hooky no seu app Flutter: uma API Dart e um widget de balão, construídos sobre os SDKs nativos do Hooky. A conversa em si é a dos SDKs iOS (SwiftUI) e Android (Jetpack Compose): nativa, sem WebView, com as cores do seu site.

Um app Flutter no Android com o widget HookyBubble no canto inferior direito. A conversa do Hooky aberta a partir de um app Flutter no iPhone, no modo escuro.
O widget HookyBubble no Android, e a conversa nativa no iPhone, no modo escuro.

Requisitos

  • Flutter 3.35 ou mais recente (Dart 3.9).
  • Android 7.0 ou mais recente (minSdk 24, o mínimo do próprio Flutter), compilado com compileSdk 36.
  • iOS 15 ou mais recente.
  • Só Android e iOS: na web e no desktop, as chamadas não fazem nada.
  • Uma conta Hooky e um site: a chave pública dele fica em Sites › Instalar, a mesma do data-site-key do balão na web.

Instalação

O pacote é instalado a partir do repositório no GitHub, como dependência git. No seu pubspec.yaml:

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

e depois flutter pub get. Os SDKs nativos de iOS e Android já vêm no pacote: nada mais a adicionar, nenhum outro repositório.

iOS

  • O plugin funciona com o Swift Package Manager (ativado por padrão nas versões recentes do Flutter) e também com o CocoaPods: ele compila o SDK iOS que traz junto, com o manifesto de privacidade.
  • Suba o target de implantação para iOS 15.0: no Xcode, target Runner › General › Minimum Deployments.
  • Para oferecer a câmera, adicione NSCameraUsageDescription ao ios/Runner/Info.plist. Sem ela, a opção simplesmente fica escondida.

Android

Nada a declarar: o SDK Android já vem incluído, com a tela de conversa, o FileProvider e a permissão INTERNET. As dependências dele (AndroidX, Compose, OkHttp) vêm do google() e do mavenCentral(), já presentes nos projetos Flutter.

O código-fonte está sob licença MIT: github.com/Defdjamel/hooky-flutter.

Comece em 3 linhas

import 'package:hooky_sdk/hooky_sdk.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  Hooky.configure('SUA_CHAVE_PUBLICA');                     // 1. na inicialização
  runApp(const MeuApp());
}

Scaffold(
  floatingActionButton: const HookyBubble(),      // 2. o balão flutuante
  …
);

FilledButton(onPressed: Hooky.open, child: const Text('Fale com a gente'));   // 3. ou o seu próprio botão

configure carrega o nome, a cor e a mensagem de boas-vindas do site, retoma a conversa salva e mantém a conexão ao vivo aberta enquanto o app está em primeiro plano. Os parâmetros nomeados dele: translation: false remove a tradução das mensagens da equipe (veja Tradução das mensagens), apiUrl só serve para testar com o seu próprio servidor.

Todas as chamadas retornam um Future e nunca geram erro: as falhas só aparecem no console, em debug. Antes de configure, identify, setAttributes e track ficam esperando, enquanto open, close e reset não fazem nada.

O balão

HookyBubble se parece com o balão nativo de cada plataforma: um quadrado arredondado com sombras suaves no iOS (60 pontos), o card com borda e sombra marcada do balão da web no Android (64 dp). Ele usa a cor do site, mostra um contador de não lidas e aparece assim que configure é chamado.

// Como botão flutuante do Scaffold
Scaffold(floatingActionButton: const HookyBubble(), …)

// Em qualquer lugar, dentro de um Stack
Stack(children: [
  const MinhaTela(),
  const Positioned(right: 20, bottom: 20, child: SafeArea(child: HookyBubble())),
])

// A sua própria ação, outro tamanho, um estilo forçado
HookyBubble(onPressed: () => analytics.log('chat'), size: 52, style: HookyBubbleStyle.material)

Abrir e fechar a conversa

Hooky.open();    // tela cheia: uma folha no iOS, uma Activity no Android
Hooky.close();   // fecha a conversa

A conversa é desenhada pelo SDK nativo: as páginas iOS e Android descrevem o que ela faz (fotos, PDFs, rascunho, erros).

Mensagens não lidas

O balão já mostra o próprio contador. Para os seus botões, Hooky.unreadCount é ao mesmo tempo um ValueListenable<int> e um Stream<int> (o valor atual, depois cada mudança). Ele volta a 0 quando a conversa é aberta.

// Um contador em uma aba
ValueListenableBuilder<int>(
  valueListenable: Hooky.unreadCount,
  builder: (context, n, _) => Badge(isLabelVisible: n > 0, label: Text('$n'), child: const Icon(Icons.chat)),
)

// Ou como stream
Hooky.unreadCount.listen((n) => print('$n não lidas'));
final agora = Hooky.unreadCount.value;

Hooky.state (um ValueListenable<HookyState>) também traz isConfigured, siteName, siteColor e sitePosition, para a sua própria interface. As respostas chegam ao vivo enquanto o app está em primeiro plano; a versão 1.0.0 não envia notificações push aos seus usuários.

Identificar o usuário

O seu app sabe quem está logado: conte isso ao Hooky. Nos detalhes da conversa, a sua equipe vê o nome, o e-mail, o seu próprio identificador do usuário, o aparelho e os dados que você anexa. Com um e-mail, a conversa não pede mais nome nem e-mail.

// Depois do login
Hooky.identify(
  name: user.nome,
  email: user.email,
  userId: user.id,                                     // o seu próprio identificador
  attributes: {'plano': 'Pro', 'pedidos': 3, 'newsletter': true},
);

// Mais tarde: mesclados com os anteriores
Hooky.setAttributes({'carrinho': 49.9, 'cupom': null}); // null apaga uma chave

// No logout
Hooky.reset();

Como funciona

  • Ainda sem conversa: tudo fica na memória (mesmo antes de configure) e é enviado com a primeira mensagem. Depois, cada mudança é enviada na hora, agrupada (um único envio para tudo o que muda no mesmo segundo). Sem conexão, ela é enviada mais tarde.
  • name e email substituem os da conversa (null os apaga no aparelho); userId: null o deixa como está.
  • Um userId diferente do da conversa é outra conta no mesmo aparelho: a conversa é esquecida, como com reset(). O novo usuário não vê a conversa do anterior.
  • Hooky.reset() fecha a conversa e esquece o histórico e a identidade. Chame no logout.

Regras dos dados

CampoRegra
Dados livresUm Map sem aninhamento: 50 chaves no máximo, 8 KB no total. As chaves novas substituem as antigas, as outras ficam.
ChaveDe 1 a 64 caracteres: letras, algarismos, espaços, _, . e -. Senão, é ignorada.
ValorUma String (cortada em 500 caracteres), um num ou um bool. null apaga a chave. O resto é descartado, com um aviso em debug.
userId200 caracteres no máximo.
name, emailO nome: 100 caracteres no máximo. O e-mail: um endereço válido, senão é ignorado.

O Hooky limpa o que passar do limite, sem nunca recusar a mensagem do seu usuário.

Esses dados são declarativos: a chave do site é pública, então qualquer pessoa pode usá-la para enviar o nome, o e-mail ou o userId que quiser. Eles ajudam a sua equipe a situar a pessoa, não provam quem ela é. Nunca coloque um segredo neles (senha, token, número de cartão) e nunca use esses dados para liberar um acesso ou agir sobre uma conta: sempre confira do seu lado.

Estatísticas

Conte o que acontece no seu app, como com Hooky.track na web: os cards aparecem em Estatísticas, junto com os do site. Chame logo depois que a ação der certo.

Hooky.track('cadastro');
Hooky.track('pedido', value: 49.9, properties: {'plano': 'pro', 'assentos': 3, 'teste': false});
  • value: um valor em dinheiro ou uma quantidade, somado no card. As propriedades não têm aninhamento (String, num, bool).
  • Chamado antes de configure, o evento fica esperando (100 no máximo). As falhas são silenciosas; sem conexão, o evento se perde.
  • As mesmas regras da web (nome, 20 propriedades no máximo, limite): veja Estatísticas na página.

Nunca envie dados pessoais (e-mail, nome, telefone) em um evento. Conte ações, não pessoas.

Tradução das mensagens

O seu usuário e a sua equipe escrevem cada um no seu idioma, como com o balão na web (veja Tradução das mensagens).

  • Abaixo de uma mensagem da equipe escrita em outro idioma que o da conversa (o do aparelho), aparece um link discreto Traduzir. Traduzida, a mensagem aparece no idioma do usuário, com “Traduzido do inglês · Ver o original” para voltar ao texto original.
  • O botão de tradução do cabeçalho oferece Traduzir sempre: as mensagens da equipe passam a chegar já traduzidas. A escolha fica salva no aparelho, para cada chave de site.
  • Uma tradução que falha mostra um erro curto com Tentar de novo. As mensagens do usuário nunca são traduzidas do lado dele; a sua equipe tem o próprio botão Traduzir, no painel e no app Hooky.
  • Só o texto é traduzido, não os anexos. A tradução vem ativada por padrão; para removê-la do seu app: Hooky.configure('SUA_CHAVE_PUBLICA', translation: false).

O que o SDK envia

O que o balão da web envia, e nada mais: as mensagens e os arquivos do usuário, o nome, o e-mail, o identificador e os dados que você informa, o idioma e o fuso horário do aparelho, e o modelo dele com a versão do sistema (“iPhone 17 Pro · iOS 27.0”, “Pixel 8 · Android 15”). Sem identificador de publicidade nem rastreamento.

  • Cada requisição leva X-Hooky-Client: hooky-flutter/1.0.0 e X-Hooky-App: <bundle id ou package> <version> (<build>): a sua equipe vê que a conversa vem de “Flutter · com.example.app 1.0 (1)”.
  • O token da conversa é guardado pelo SDK nativo: no Keychain no iOS (só neste aparelho), em um arquivo privado nunca salvo em backup no Android.
  • O plugin traz um manifesto de privacidade iOS (PrivacyInfo.xcprivacy) que não declara nenhum rastreamento.
  • Para as páginas das suas lojas, os dados são os dos SDKs nativos: veja a Privacidade do app na App Store (página iOS) e a seção Segurança dos dados do Google Play (página Android).

Idioma, modo escuro, acessibilidade

Idioma
A interface existe em francês, inglês, espanhol, português, italiano e alemão. Ela segue o idioma do aparelho, com inglês por padrão. O idioma vai com a primeira mensagem, para que a sua equipe responda no idioma certo e na hora certa.
Modo escuro
A conversa e o balão seguem o modo claro ou escuro do aparelho, com a cor do seu site.
Acessibilidade
O balão é um botão para os leitores de tela (“Abrir o chat com …”, e o número de não lidas); a conversa nativa mantém VoiceOver e Dynamic Type no iOS, TalkBack e o tamanho do texto no Android.

R8 e ProGuard

Nada a adicionar no Android: o SDK não usa reflexão e traz as próprias regras.

Solução de problemas

O meu site cadastrou os endereços dele: o app é recusado?
Não. Um app não tem endereço: ele se apresenta pelo cabeçalho X-Hooky-Client e funciona mesmo quando o site cadastrou os domínios dele. Não adicione nada em Sites.
“the Hooky plugin is not available on this platform”
Depois de adicionar o pacote, reinicie o app por completo (flutter run): um hot reload não basta. Na web e no desktop, o plugin não existe.
A compilação iOS falha
Confira o target de implantação (iOS 15.0): o Swift Package Manager e o CocoaPods recusam um menor. Depois de mudar a versão do pacote, rode flutter clean e depois flutter pub get.
O balão fica com as cores padrão
Confira a chave pública do site (não a chave secreta hks_…, nem uma chave de API hka_…) e a rede.
Sem conexão
A mensagem mostra o erro e fica no campo; identify e setAttributes são enviados mais tarde. Os eventos track enviados sem conexão se perdem.

Histórico de versões

1.0.0
Primeira versão: Hooky.configure, identify (com userId e dados livres), setAttributes, open, close, track, reset, unreadCount e state ao vivo, o widget HookyBubble, a tradução das mensagens da equipe (translation), sobre os SDKs nativos iOS 1.0.0 e Android 1.0.0.