Skip to main content

hatang_chat 0.2.1 copy "hatang_chat: ^0.2.1" to clipboard
hatang_chat: ^0.2.1 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.

Beta (0.1). Chat screen, launcher, identity, events, push and deep links are in. Photo and file attachments, product/order/payment cards and the conversation list arrive in 0.2.

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.

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'). From the Android emulator use http://10.0.2.2:3002. iOS needs an App Transport Security exception for plain http 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