hatang_chat 0.2.2
hatang_chat: ^0.2.2 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.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:
- Open the Hatang dashboard, go to your website's settings, then the Mobile apps tab.
- Create an app key (
hak_…). It is shown once. - 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.
Deep links #
"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 #
- Documentation (Vietnamese): https://docs.hatang.dev/sdk/flutter
- Example QA app:
example/ - Changelog: CHANGELOG.md
- Issues: https://github.com/sota-labs/hatang/issues
Maintained by Sota Labs. MIT licensed.