Skip to main content

hatang_chat 0.2.4 copy "hatang_chat: ^0.2.4" to clipboard
hatang_chat: ^0.2.4 copied to clipboard

Hatang live chat for Flutter apps: a native customer-support chat screen that talks to your Hatang site over REST and WebSocket, with identity, events, presence and push hooks.

hatang_chat #

Native live chat for Flutter apps, connected to the Hatang support inbox. Customers chat from your app into the same inbox your team uses for your website, Zalo, Messenger and TikTok Shop.

  • Pure Dart, no WebView. The SDK speaks Hatang's chat protocol directly, so the chat screen is a native Flutter widget on iOS, Android, web, macOS, Windows and Linux.
  • Keeps the conversation. The visitor token is kept in secure storage, and history resumes after restarts and reconnects. Messages sent while offline are retried without duplicates.
  • Knows who is writing. identify() with a signature from your backend links the chat to your user account across devices.
  • Push notifications through your own Firebase project, only while the app is in the background.
  • Vietnamese and English built in, a theme taken from your site's brand colour, and light and dark modes.

Chat screen with product and order cards The same chat in dark mode The visitor's conversations Chat button with an unread badge in a host app

Beta (0.2). Chat screen and launcher, identity, events, push, deep links, photos and files, product/order/payment cards with in-app purchase hooks, and the conversation list are in. The API may still change before 1.0.

Try it without an account #

The example app talks to Hatang's public demo inbox, where an AI assistant answers questions about Hatang and messages are deleted after 24 hours:

dart pub unpack hatang_chat
cd hatang_chat-*/example
flutter run

Pass --dart-define=HATANG_SITE_ID=… --dart-define=HATANG_APP_KEY=… to point it at your own site.

Before you start #

You need a Hatang account with a website, and an app key for that website:

  1. Open the Hatang dashboard, go to your website's settings, then the Mobile apps tab.
  2. Create an app key (hak_…). It is shown once.
  3. Note the Site ID (site_…) shown on the same page.

The app key replaces the browser's origin check: it identifies your app to the chat gateway. It is not a secret like an API key, but it can be revoked and replaced from the dashboard.

Install #

flutter pub add hatang_chat

iOS and macOS need nothing extra for chat. Android needs the INTERNET permission, which Flutter apps have by default in release builds.

Quick start #

import 'package:flutter/material.dart';
import 'package:hatang_chat/hatang_chat.dart';

final navigatorKey = GlobalKey<NavigatorState>();

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await HatangChat.configure(
    siteId: 'site_…',
    appKey: 'hak_…',
    navigatorKey: navigatorKey,
  );
  runApp(MaterialApp(navigatorKey: navigatorKey, home: const HomePage()));
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          Center(
            child: FilledButton(
              onPressed: () => HatangChat.open(context),
              child: const Text('Chat with us'),
            ),
          ),
          // A floating chat button with an unread badge.
          const HatangChatLauncher(),
        ],
      ),
    );
  }
}

Identify your users #

Call identify after sign-in. Compute hash on your server as the hex HMAC-SHA256 of the user id with the site's identity secret (dashboard, website settings). Never ship the secret in the app.

await HatangChat.instance.identify(HatangUser(
  userId: user.id,
  email: user.email,
  name: user.name,
  hash: hashFromYourBackend,
));

// On sign-out: forget this visitor and start a fresh anonymous session.
await HatangChat.instance.reset();

Unsigned identities are stored as claims only. If the same signed user already chatted on another device, the two sessions merge and the conversation history follows them.

Events and data #

HatangChat.instance.events.listen((event) {
  switch (event) {
    case HatangMessageReceived(:final message):
      debugPrint('agent says: ${message.content}');
    case HatangConversion(:final kind):
      analytics.log(kind.name); // chatStarted, lead, purchase
    default:
      break;
  }
});

HatangChat.instance.unreadCount.listen((n) => setBadge(n));
await HatangChat.instance.setData({'plan': 'pro', 'orders': 12});
await HatangChat.instance.pushEvent('viewed_product', {'sku': 'TS-100'});
HatangChat.instance.setScreen('checkout'); // shown to agents on the Live board

Push notifications #

Upload your Firebase service account JSON in the dashboard (website settings, Mobile apps tab). Then hand the device token to the SDK, and pass notification taps back to it:

final token = await FirebaseMessaging.instance.getToken();
if (token != null) {
  await HatangChat.instance.setPushToken(token, provider: PushProvider.fcm);
}

FirebaseMessaging.onMessageOpenedApp.listen((m) {
  if (HatangChat.isHatangNotification(m.data)) HatangChat.instance.handleNotification(m.data);
});
final initial = await FirebaseMessaging.instance.getInitialMessage();
if (initial != null && HatangChat.isHatangNotification(initial.data)) {
  await HatangChat.instance.handleNotification(initial.data);
}

Replies arrive as a notification only while the app is in the background or showing another screen. The SDK tells the gateway when the app goes to the background.

On iOS, push needs, besides the Firebase iOS app:

  • an App ID whose bundle ID is exactly your app's, with Push Notifications enabled (FCM answers every send with APNs TopicDisallowed when the App ID does not match, e.g. a stray trailing dot);
  • an APNs authentication key (Sandbox & Production) uploaded to Firebase with its Key ID and Team ID;
  • the Push Notifications capability and the Remote notifications background mode in Xcode.

On Android, Hatang sends replies on the notification channel hatang_messages. Create a channel with that id (for example with flutter_local_notifications) to give it a name and importance in the phone's settings; without it Android shows them on Firebase's fallback channel.

handleNotification returns as soon as the chat is pushed (or parked until the SDK is ready on a cold start), so awaiting it in start-up code is safe.

The example app's Push tab shows the token, lets you paste one, and simulates notification taps. On Android, the example run against the public demo receives real notifications out of the box: it uses the demo's Firebase project, so the demo assistant's replies arrive while the app is in the background. For your own site, add an Android or iOS app to your Firebase project and pass its options at build time:

flutter run --dart-define=FIREBASE_API_KEY=… --dart-define=FIREBASE_APP_ID=… \
  --dart-define=FIREBASE_PROJECT_ID=… --dart-define=FIREBASE_SENDER_ID=…

Conversation history #

HatangChat.openInbox(context) shows the customer's conversations, newest first, with who answered, the last message, unread counts and resolved ones. Put HatangChatInbox() in your own tab instead if you prefer. HatangChat.open(context, conversationId: id) opens a specific one; scrolling up in a conversation loads earlier messages.

Photos and files #

The composer has an attach button: camera (phones), photo library and files. iPhone HEIC photos are converted to JPEG and big photos scaled to 2048 px before upload; files up to 10 MB of the usual types (images, PDF, Office, CSV, zip, mp4, m4a, mp3) are accepted. Add the usage strings to ios/Runner/Info.plist:

<key>NSPhotoLibraryUsageDescription</key>
<string>Send photos to our support team</string>
<key>NSCameraUsageDescription</key>
<string>Take a photo to send to our support team</string>

To send a file from your own UI: controller.attach(UploadSource(bytes: …, name: 'invoice.pdf', mimeType: 'application/pdf')).

Selling in the app: cards and in-app purchase #

Your team can send product, order and payment cards from the inbox. By default "Buy" writes "I'd like to buy …" in the composer, and a payment card shows a VietQR code and a "Pay now" button that opens the web checkout. That suits physical goods and real-world services.

App stores require digital goods and features used inside the app to be sold through in-app purchase (Apple App Store Review Guideline 3.1.1, Google Play's payments policy). For those, hand the purchase to your own store integration and hide external payment:

await HatangChat.configure(
  siteId: 'site_…',
  appKey: 'hak_…',
  commerce: HatangCommerce(
    showExternalCheckout: !Platform.isIOS, // no QR, transfer note or web checkout on iOS
    onBuyProduct: (context, product) async {
      // Set per product in the dashboard's product form.
      final storeId = Platform.isIOS ? product.appStoreProductId : product.googlePlayProductId;
      if (storeId == null) return false;       // not an in-app product: default behaviour
      await myIapService.buy(storeId);          // e.g. package:in_app_purchase
      return true;
    },
    onPay: (context, payment) async => false,   // or handle payment cards yourself
  ),
);

Return true from a hook when you handled the tap. The SDK itself has no store dependency.

"We replied" emails contain a link like https://chat.hatang.dev/c/<token>. If your app handles that link, restore the session with it:

final token = HatangChat.linkTokenFromUri(uri);
if (token != null) await HatangChat.instance.restoreSession(token);

Embedding the chat in your own screen #

HatangConversationView is the conversation without an app bar, for a "Support" tab or a bottom sheet. It needs a controller:

final controller = HatangChatController(HatangChat.instance.engine);

Scaffold(
  appBar: AppBar(title: const Text('Support')),
  body: HatangConversationView(controller: controller),
  bottomNavigationBar: myTabBar,
);

Mount it only while it is visible (not in an IndexedStack that keeps hidden tabs alive), or call controller.setVisible(false) when its tab is hidden: a visible chat marks messages as read. HatangChat.instance.setBottomPadding(56) lifts the launcher and the chat screen above a tab bar or bottom sheet of yours; views already on screen follow the change.

Theme and language #

await HatangChat.configure(
  siteId: 'site_…',
  appKey: 'hak_…',
  locale: HatangLocale.vi,                // default: saved choice, site default, then device
  themeMode: HatangChatThemeMode.system,  // default: follows the site setting
  theme: HatangChatTheme.light(accent: const Color(0xFF0EA5E9)),
);

await HatangChat.instance.setLocale(HatangLocale.en);

Local development #

Point the SDK at a local gateway with baseUrl: Uri.parse('http://localhost:3002'). The gateway answers bootstrap with a WebSocket URL built from its own CHAT_URL, so the phone must reach the gateway at that same address:

  • Android emulator: run adb reverse tcp:3002 tcp:3002 and keep localhost (the emulator's 10.0.2.2 reaches the REST API but not the socket, which still points at localhost).
  • Physical phone on Wi-Fi: set CHAT_URL on the gateway to your machine's LAN address (for example http://192.168.1.20:3002) and use that as baseUrl.
  • iOS: plain http needs an App Transport Security exception in debug builds; see example/ios/Runner/Info.plist.

More #

Maintained by Sota Labs. MIT licensed.

0
likes
0
points
730
downloads

Publisher

verified publisherhatang.dev

Weekly Downloads

Hatang live chat for Flutter apps: a native customer-support chat screen that talks to your Hatang site over REST and WebSocket, with identity, events, presence and push hooks.

Repository (GitHub)
View/report issues

Topics

#chat #live-chat #customer-support #messaging #websocket

License

unknown (license)

Dependencies

clock, device_info_plus, file_picker, flutter, flutter_image_compress, flutter_secure_storage, http, http_parser, image_picker, meta, package_info_plus, shared_preferences, url_launcher, web_socket_channel

More

Packages that depend on hatang_chat