Chatasist Flutter
Native Flutter widget SDK for Chatasist chat with a simple initialize then present integration flow, fully powered by Chatasist Widget APIs.
Features
- Native chat UI (chat list, thread, composer)
- Session bootstrap with JWT persistence; optional server-minted JWT before first session
- File/image/video/audio attachments (multipart to
/api/widget/messages) - Realtime integration via Pusher (
/api/widget/broadcasting/auth) - Voice/video call signaling support using WebRTC + widget call endpoints
- In-thread CSAT survey flows (rating + feedback) aligned with the web widget
- Rich HTML in widget surfaces via
flutter_html; links opened withurl_launcher - Home, messages, news, and help tabs with consistent layout tokens
- Global plugin singleton + unread/incoming call notifiers
SDK and Platform Support
- SDK: Flutter package
- Platforms: Android, iOS, Web (host app support depends on your app/plugin stack)
- Tested package baseline:
- Flutter
3.35+ - Dart
3.11+
- Flutter
Requirements
- Flutter 3.35+
- Backend endpoints under
/api/widget/* - Required request header:
X-Chatasist-Channel: {channel_uuid} - Optional identity support through
/api/widget/session
Install
Add chatasist_flutter only; HTML and URL handling dependencies are pulled in transitively (you do not add flutter_html or url_launcher to your app unless you use them yourself).
From pub.dev (use the current version from the package page):
dependencies:
chatasist_flutter: ^1.0.11
For local development:
dependencies:
chatasist_flutter:
path: ../chatasist-flutter
Quick start
By default the widget opens as a bottom card (sheet over a dimmed backdrop). Use presentation: ChatasistWidgetPresentation.fullscreen for a classic full-screen modal.
final navigatorKey = GlobalKey<NavigatorState>();
await chatasistFlutter.initialize(
ChatasistWidgetOptions(
baseUrl: 'https://chatasist.com',
channelId: '00000000-0000-0000-0000-000000000001',
language: 'en',
navigatorKey: navigatorKey,
// Optional: bind the first session to a server-minted customer JWT
// initialSessionJwt: widgetJwtFromYourBackend,
// Optional: ChatasistWidgetPresentation.fullscreen
),
);
await chatasistFlutter.setIdentity('visitor@example.com');
await chatasistFlutter.present();
Usage
Import package:chatasist_flutter/chatasist_flutter.dart and use the singleton:
import 'package:flutter/material.dart';
import 'package:chatasist_flutter/chatasist_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final navigatorKey = GlobalKey<NavigatorState>();
await chatasistFlutter.initialize(
ChatasistWidgetOptions(
baseUrl: 'https://chatasist.com',
channelId: 'YOUR_WIDGET_CHANNEL_UUID',
language: 'en',
navigatorKey: navigatorKey,
),
);
runApp(MyApp(navigatorKey: navigatorKey));
}
Changing the language
The language starts from ChatasistWidgetOptions.language. To switch it later — for example when the user changes the app language — call:
await chatasistFlutter.setLanguage('ru');
The widget reloads its session so text, dates, news, help topics and AI replies follow the new language. There is no need to call initialize() again; if you do, the previous connection is closed first.
ChatasistWidgetOptions
| Field | Required | Description |
|---|---|---|
baseUrl |
no | Base URL of your Chatasist backend (widget APIs). Defaults to https://app.chatasist.com. |
channelId |
yes | Widget channel UUID (X-Chatasist-Channel). |
language |
no | Preferred language (az, en, ru, tr; regional forms like az-AZ are accepted). Drives the plugin UI, dates, news/help content and AI reply language. The backend resolves it against the languages enabled for the channel (widget_locale); unsupported values fall back to the channel default. |
navigatorKey |
no | Strongly recommended: root navigator used by present() / dismiss(). |
initialSessionJwt |
no | Optional JWT from your backend (e.g. POST /api/v1/widget/session-jwt) so the first session binds to an API-created customer. |
customerToken |
no | Token your backend issued for the signed-in customer, forwarded to your MCP server as X-Customer-Token. See Signed-in customers. |
customerTokenProvider |
no | Future<String?> Function() the plugin calls to get a fresh customer token. Preferred for short-lived tokens. |
customerTokenRefreshInterval |
no | How long a provided token is reused before the provider is asked again. Defaults to 10 minutes. |
presentation |
no | ChatasistWidgetPresentation.card (default) or .fullscreen. |
debug |
no | Verbose diagnostics for local debugging. |
Common runtime actions
- Initialize once at app startup.
- Optionally set visitor identity with
setIdentity. - After minting or rotating a widget JWT on the server, call
setSessionJwtto persist it and reload session/chats. - When a customer signs in to your app, give the plugin their customer token (
customerTokenProvider, orsetCustomerToken); clear it withsetCustomerToken(null)on sign-out. - Open the messenger with
present(). Prefer a configurednavigatorKey; otherwise pass aBuildContextwith access to aNavigator(see API below). - When the user logs out of your app or you tear down the SDK, call
dispose()to release listeners and internal state.
Signed-in customers (customer token)
If your workspace connects its own data through an MCP server, the AI agent can answer questions about the customer's own records — "where is my order?", "show my orders". For that, your server has to know which customer is asking. In a mobile app the customer is usually already signed in, so let the plugin carry that sign-in instead of making them confirm by code.
Mint a short-lived token on your backend for the signed-in customer and hand it to the
plugin. Every request then carries it as X-Customer-Token, and Chatasist forwards it
unchanged to your MCP server on each tool call. Chatasist never inspects the token.
Recommended — a provider. The plugin calls it when it needs a token and again before the old one goes stale, so you do not need a timer:
await chatasistFlutter.initialize(
ChatasistWidgetOptions(
channelId: 'YOUR_CHANNEL_UUID',
navigatorKey: navigatorKey,
// Called on the first request, then again every `customerTokenRefreshInterval`.
customerTokenProvider: () async {
if (!auth.isSignedIn) return null; // nobody signed in: send no token
return await myApi.mintCustomerToken(); // your backend, your login token
},
// Keep this comfortably below your token's lifetime (default: 10 minutes).
customerTokenRefreshInterval: const Duration(minutes: 10),
),
);
Or set it yourself when you already manage the token's lifecycle:
chatasistFlutter.setCustomerToken(token); // after sign-in, and after each refresh
chatasistFlutter.setCustomerToken(null); // on sign-out
Notes:
- The token is kept in memory only; it is never written to device storage.
- A provider that throws does not block chat: the previous token keeps being sent and the provider is retried after 30 seconds.
- If a token expires anyway, nothing breaks. Your MCP server rejects it, Chatasist repeats the lookup without it, and the customer is asked to confirm by code instead.
- Never give the plugin your customer's login token. Exchange it on your backend for a token that only identifies the customer to your MCP server.
Android Setup
Ensure your Android app has required permissions for network, attachments, and call/media features.
Recommended permissions in AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
Notes:
- Keep AndroidX enabled in Gradle settings.
- Confirm your app
minSdkis compatible with transitive plugin requirements (especially media/WebRTC plugins). - Request runtime permissions where needed (camera, microphone, storage/media).
iOS Setup
Add usage descriptions in Info.plist for features your app enables:
<key>NSCameraUsageDescription</key>
<string>Camera access is required for video calls.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Microphone access is required for voice and video calls.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Photo library access is required to attach media.</string>
Notes:
- Ensure your iOS deployment target is compatible with dependency requirements.
- Request permissions before launching related features.
- If chat content opens non-https URL schemes, you may need to declare them under
LSApplicationQueriesSchemesper url_launcher iOS documentation.
Foreground new-message notifications
While your app is in the foreground, the widget keeps a live realtime
connection, so agent/AI replies arrive instantly with no push infrastructure.
Listen to chatasistFlutter.onNewMessage to show your own in-app toast/banner
and open the widget on tap. The stream emits only inbound (agent/AI) messages —
the customer's own messages are excluded — and fires only while the app is
foregrounded (the socket is suspended in the background).
Use chatasistFlutter.isPresenting to skip the toast while the user is already
viewing the widget. WidgetMessage.content may contain HTML, so strip tags for
a one-line preview.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:chatasist_flutter/chatasist_flutter.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
StreamSubscription<WidgetMessage>? _sub;
@override
void initState() {
super.initState();
_sub = chatasistFlutter.onNewMessage.listen(_onNewMessage);
}
void _onNewMessage(WidgetMessage m) {
// Don't toast if the widget is already open (the reply is visible in-thread).
if (chatasistFlutter.isPresenting || !mounted) return;
final String sender =
(m.senderName?.trim().isNotEmpty ?? false) ? m.senderName! : 'Support';
final String preview = m.content
.replaceAll(RegExp(r'<[^>]*>'), ' ')
.replaceAll(RegExp(r'\s+'), ' ')
.trim();
// Replace with your own toast/banner; tap opens the widget.
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('$sender: $preview', maxLines: 2),
action: SnackBarAction(
label: 'Open',
onPressed: () => chatasistFlutter.present(),
),
),
);
}
@override
void dispose() {
_sub?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) =>
const Scaffold(body: Center(child: Text('Your app content')));
}
An unread badge can be driven off chatasistFlutter.unreadCount the same way
(see the API section).
Background Push Notification Setup
This package handles in-app realtime via widget APIs/Pusher. For background push
notifications (app closed or backgrounded, when the realtime socket is
suspended), integrate native push infrastructure in the host app (for example
firebase_messaging) and route notification taps into opening Chatasist.
Typical flow:
- Configure push notifications in your Flutter host app (
firebase_messaging, APNS/FCM setup). - Use your backend to deliver notification payloads for new widget messages/calls.
- On notification tap, call
chatasistFlutter.present()to open the widget.
Current scope:
- In-app realtime events (and the foreground
onNewMessagestream above) are supported by this package. - Push token registration and push delivery orchestration are handled by your app/backend stack.
API
Plugin (chatasistFlutter)
Future<void> initialize(ChatasistWidgetOptions options)Future<void> setIdentity(String? identity)— sets or clears visitor identity and reboots session state.Future<void> setSessionJwt(String? jwt)— updates the stored widget JWT and reloads session/chats.void setCustomerToken(String? token)— sets, replaces, or clears the signed-in customer's token (sent asX-Customer-Token). Does not reload the conversation.Future<void> present({BuildContext? context})— presents the widget route. UsesnavigatorKeyfrom options when set; otherwisecontext(root navigator).void dismiss()— pops the widget route when the navigator key allows it.Future<void> refresh()— reloads session and chats from the backend.Future<void> dispose()— disposes controller, realtime, and notifiers; call before dropping the plugin for the app lifetime.unreadCount—ValueNotifier<int>for unread messages across chats.hasIncomingCall—ValueNotifier<bool>for incoming call presence.onNewMessage—Stream<WidgetMessage>of new agent/AI replies received live (foreground only); use for in-app toasts. Safe to subscribe beforeinitialize.isPresenting—bool, whether the widget UI is currently on screen (use to suppress toasts).
Types
ChatasistWidgetPresentation—card|fullscreen.ChatasistCustomerTokenProvider—Future<String?> Function(), seecustomerTokenProvider.
Backend endpoint mapping
POST /api/widget/sessionGET /api/widget/chatsGET /api/widget/chats/{id}POST /api/widget/messagesPOST /api/widget/broadcasting/authPOST /api/widget/chats/{chat}/calls/startPOST /api/widget/chats/{chat}/calls/{call}/answerPOST /api/widget/chats/{chat}/calls/{call}/icePOST /api/widget/chats/{chat}/calls/{call}/end
Reference
- Flutter package page structure inspiration: pub.dev
Libraries
- chatasist_flutter
- Public entrypoint for the Chatasist Flutter plugin package.