Cerqle Chat
A customizable, battery-efficient Flutter SDK for embedding Cerqle customer support chat into mobile, web, and desktop apps. It provides identity-scoped visitor sessions, Pusher-powered real-time messaging, prebuilt customizable UI, and push notifications.
Capabilities & Platform Support
| Capability | Android / iOS | Web | macOS / Windows / Linux |
|---|---|---|---|
| Anonymous & Signed-User Sessions | ✅ Yes | ✅ Yes* | ✅ Yes |
| OneSignal Push Notifications | ✅ Yes | ✅ Yes | ✅ Yes |
| Realtime Pusher Streaming | ✅ Yes | ✅ Yes | ✅ Yes |
| Text, Image, Audio & File Messaging | ✅ Yes | ✅ Yes | ✅ Yes |
| Pusher Realtime Sync | ✅ Yes | ✅ Yes | ✅ Yes |
| Typing Indicators & Human Handoff | ✅ Yes | ✅ Yes | ✅ Yes |
| Prebuilt UI (Screens, Sheets, Dialogs, Launchers) | ✅ Yes | ✅ Yes | ✅ Yes |
| Required Pre-Chat Lead Forms | ✅ Yes | ✅ Yes | ✅ Yes |
* Web secure storage requires HTTPS (or localhost during development) and is scoped to the browser origin.
Installation
Add the package to your pubspec.yaml:
dependencies:
cerqle_chat: ^0.1.3
Or run:
flutter pub add cerqle_chat
Platform Requirements
- Android: Set
minSdk = 23inandroid/app/build.gradle(required byflutter_secure_storage) and ensureINTERNETpermission is granted:defaultConfig { minSdk = 23 } - iOS & macOS: Enable Keychain Sharing in Xcode and include a
keychain-access-groupsentitlement. The runnable example contains the required configuration. - Web: Deploy over HTTPS (browser session storage inherits the origin's security).
For built-in voice recording, Android hosts must declare
android.permission.RECORD_AUDIO and use compileSdk 35 or newer.
iOS hosts must provide NSMicrophoneUsageDescription. CocoaPods hosts must also
activate microphone support in the existing post_install build-configuration
loop (as shown in example/ios/Podfile):
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)']
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] += [
'PERMISSION_MICROPHONE=1',
]
A denied microphone prompt closes silently. A later attempt with permanently denied access shows a Settings snackbar. Permission is checked again on every attempt.
Quick Start
Create one configuration and initialize Cerqle before runApp so push handling,
API branding, visitor registration, and realtime unread state are ready:
import 'package:flutter/material.dart';
import 'package:cerqle_chat/cerqle_chat.dart';
final navigatorKey = GlobalKey<NavigatorState>();
const config = CerqleConfig(widgetKey: 'YOUR_WIDGET_KEY');
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await CerqleChat.initialize(
config: config,
navigatorKey: navigatorKey,
);
runApp(MaterialApp(navigatorKey: navigatorKey, home: const MyHomePage()));
}
Open chat from any widget with
await CerqleChat.open(context, config: config), or use one of the integration
surfaces below. If push handling and pre-open unread state are unnecessary,
direct CerqleChat.open(...) usage remains supported.
Note
The widgetKey is a public routing identifier, not a secret. Never bundle Cerqle management credentials or widget secret keys in client applications.
Integration Styles
Cerqle provides multiple ready-to-use presentation modes to fit seamlessly into any app workflow:
1. Full Screen
An immersive, dedicated support page with app bar navigation:
import 'package:flutter/material.dart';
import 'package:cerqle_chat/cerqle_chat.dart';
void openFullScreen(BuildContext context, CerqleConfig config) {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => CerqleChatScreen(config: config),
),
);
}
2. Modal Bottom Sheet
Keeps the current screen in context while sliding up the chat interface:
import 'package:flutter/material.dart';
import 'package:cerqle_chat/cerqle_chat.dart';
Future<void> openBottomSheet(BuildContext context, CerqleConfig config) async {
await CerqleChat.open(
context,
config: config,
presentation: CerqlePresentation.bottomSheet,
);
}
3. Dialog Popup
A compact, centered chat window ideal for tablets, desktops, or web:
import 'package:flutter/material.dart';
import 'package:cerqle_chat/cerqle_chat.dart';
Future<void> openDialog(BuildContext context, CerqleConfig config) async {
await CerqleChat.open(
context,
config: config,
presentation: CerqlePresentation.dialog,
);
}
4. Floating Launcher
An expandable floating action button that overlays your screen:
import 'package:flutter/material.dart';
import 'package:cerqle_chat/cerqle_chat.dart';
Widget buildFloatingLauncher(CerqleConfig config) {
return Stack(
children: [
const Placeholder(), // Application content
CerqleChatLauncher(config: config),
],
);
}
The launcher can display an unread indicator when an agent replies while the
chat is closed. It keeps listening through the controller's realtime session,
so the indicator does not depend on push-notification delivery. Opening the
chat from the launcher or another entry point marks the visible replies as read
and clears every registered badge for the same configuration. Enable it with
showBadge: true:
Display a count and customize its appearance when needed:
CerqleChatLauncher(
config: config,
showBadge: true,
badgeShowCount: true,
badgeMaxCount: 99,
badgeBackgroundColor: Colors.red,
badgeTextColor: Colors.white,
badgeOffset: const Offset(6, -6),
)
The default is showBadge: false. A custom launcher builder can read
state.unreadCount or state.hasUnreadMessages directly.
For an application-owned button, keep a controller alive and wrap the button:
CerqleChat.badge(
config: config,
showCount: true,
child: IconButton(
icon: const Icon(Icons.chat),
onPressed: () => CerqleChat.open(
context,
config: config,
),
),
)
The badge owns its runtime and releases it when removed from the widget tree.
Headless chat implementations can still use state.unreadCount and call
await controller.markRead() when their conversation UI becomes visible.
5. Embedded View
Place the chat view directly inside an existing layout, drawer, or split-view:
import 'package:flutter/material.dart';
import 'package:cerqle_chat/cerqle_chat.dart';
Widget buildEmbeddedChat(CerqleConfig config) {
return CerqleChatView(
config: config,
showHeader: true,
);
}
6. Headless & Custom UI
Take full programmatic control with CerqleChatController:
import 'package:cerqle_chat/cerqle_chat.dart';
Future<void> runHeadlessChat(CerqleConfig config) async {
final client = CerqleClient(config: config);
final controller = CerqleChatController(client: client);
// Listen to state changes
final subscription = controller.states.listen((state) {
debugPrint('Phase: ${state.phase}, Messages: ${state.messages.length}');
});
await controller.initialize();
await controller.sendText('Hello, I need help!');
// Cleanup
await subscription.cancel();
await controller.dispose();
await client.close();
}
Configuration Reference
CerqleConfig accepts the following options:
| Property | Type | Default | Description |
|---|---|---|---|
widgetKey |
String |
(required) | Public routing identifier issued by the Cerqle dashboard. |
apiBaseUrl |
String |
'https://cerqle.ai' |
Base origin endpoint for widget API requests (/widget/v1/*). |
user |
CerqleUser? |
null |
Visitor identity, profile data, and HMAC signature for verified users. |
theme |
CerqleThemeData? |
null |
Presentation overrides for colors, bubble radius, spacing, and brightness. |
useApiColors |
bool |
true |
When true, applies the dashboard-configured branding palette automatically. |
lightStatusBarIcons |
bool |
false |
Uses white status-bar icons and text in full-screen chat. Enable it for dark or strongly colored headers. |
presentation |
CerqlePresentation |
CerqlePresentation.fullScreen |
Default modal style (fullScreen, bottomSheet, or dialog) used by CerqleChat.open. |
enableTyping |
bool |
true |
Whether the controller publishes throttled visitor typing updates. |
mediaAdapter |
CerqleMediaAdapter? |
null |
Optional override for the SDK's built-in image picker, voice recorder, and file picker. |
diagnostics |
CerqleDiagnosticsCallback? |
null |
Callback receiving redacted operational metrics and lifecycle events. |
oneSignalAppId |
String |
CerqleConfig.defaultOneSignalAppId |
OneSignal App ID used for push notification registration. |
enableOneSignal |
bool |
true |
Whether device push notification tokens are registered on session start. |
requireNotificationPermission |
bool |
true |
When true, notification permission is required to open a modal chat. Denial keeps chat closed; if the OS prompt is unavailable, a compact message links to notification settings. |
registerUserOnStartup |
bool |
true |
Whether initialization registers the visitor and loads API branding/realtime unread state before first open. When false, launchers use local fallback branding until chat opens. |
sessionStore |
CerqleSessionStore? |
null |
Custom session store override (defaults to secure encrypted platform storage). |
Key Features
👤 Verified & Authenticated Users
To associate chat sessions with registered users in your application, provide a CerqleUser along with an HMAC signature computed on your backend:
import 'package:cerqle_chat/cerqle_chat.dart';
final config = CerqleConfig(
widgetKey: 'YOUR_WIDGET_KEY',
user: CerqleUser(
externalId: 'user_123',
name: 'Jane Doe',
email: 'user@example.com',
signature: 'backend_hmac_signature',
),
);
Switching Accounts & Logout
- Switch user: Call
controller.updateUser(newUser)when switching accounts. - Logout: Call
controller.updateUser(null)on logout to wipe active credentials and clear local conversation state securely.
🎨 Colors & Theming
By default, the SDK uses the color palette configured in your Cerqle dashboard (useApiColors: true).
To customize colors locally or use custom themes:
import 'package:flutter/material.dart';
import 'package:cerqle_chat/cerqle_chat.dart';
final config = CerqleConfig(
widgetKey: 'YOUR_WIDGET_KEY',
useApiColors: false, // Disables server palette
theme: const CerqleThemeData(
primaryColor: Color(0xFF6B46C1),
visitorBubbleColor: Color(0xFF6B46C1),
agentBubbleColor: Color(0xFFE9ECEF),
borderRadius: 16.0,
),
);
🔔 Push Notifications
The SDK provides built-in OneSignal push notification integration so visitors receive notifications when agents reply.
Initialize Cerqle once in main(). Notification handlers and visitor
registration are managed internally:
import 'package:flutter/material.dart';
import 'package:cerqle_chat/cerqle_chat.dart';
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final config = CerqleConfig(
widgetKey: 'YOUR_WIDGET_KEY',
user: const CerqleUser(name: 'Demo User', email: 'user@demo.com'),
requireNotificationPermission: true,
);
await CerqleChat.initialize(
config: config,
navigatorKey: navigatorKey,
);
runApp(MaterialApp(navigatorKey: navigatorKey, home: const Scaffold()));
}
When a notification is tapped, the SDK automatically opens the chatbox. Live updates continue through Pusher while the chat is active.
Visitor registration runs automatically by default. Set
registerUserOnStartup: false to skip eager registration during
CerqleChat.initialize. Launchers and custom badges also remain unregistered
until chat is opened, so API-provided launcher colors and assets are unavailable
and the launcher uses the local fallback theme before that first open.
Consequently, an unread badge cannot receive realtime updates before that first
open because no visitor session exists yet. Set
enableOneSignal: false to disable OneSignal initialization and notification
handling.
The SDK uses CerqleConfig.defaultOneSignalAppId by default. To use a
different OneSignal application, pass its public app ID through
CerqleConfig.oneSignalAppId. The runnable example reads this value from
CERQLE_ONESIGNAL_APP_ID in its .env file.
For full-screen chat on a dark or strongly colored header, set
lightStatusBarIcons: true. The status bar remains transparent; this option
changes only its icon and text brightness and does not affect embedded, dialog,
or bottom-sheet presentations.
📷 Media & Attachments
Image picking, voice messaging, and document attachments work out of the box. No media configuration is required:
import 'package:cerqle_chat/cerqle_chat.dart';
final config = CerqleConfig(
widgetKey: 'YOUR_WIDGET_KEY',
);
For custom picker or recorder behavior, implement CerqleMediaAdapter and
pass it through mediaAdapter. This replaces the SDK default.
Native voice-message playback and previews use temporary audio files with format detection, which are cleaned up when the player closes. Web uses data-URI playback.
🔗 Clickable Message Links
Sent and received text messages automatically recognize http://, https://,
and www. links. Link text is blue and underlined, remains selectable with the
rest of the message, and opens in the platform's external browser. A leading
scheme is added as https:// for www. links, while sentence punctuation is
kept outside the destination URL.
Human Support & Conversation Activities
The default chat UI shows a compact human-support banner when handoff is
available. Visitors can select Talk to an agent, see request and connection
status, and retry a failed request. Headless or custom UI integrations can call
controller.requestHumanAgent() and observe state.handoff.
Messages marked as activities by the backend (kind: activity) expose
CerqleMessage.isActivity and appear as centered text in the conversation.
📝 Pre-Chat Forms
When a widget requires pre-chat information (such as name or email), the built-in UI collects and submits the required fields automatically before initiating chat. Known fields already set on config.user are automatically populated.
For headless integrations, submit manually via:
import 'package:cerqle_chat/cerqle_chat.dart';
Future<void> submitLead(CerqleChatController controller) async {
await controller.submitPreChat(
const CerqlePreChatData(name: 'Jane Doe', email: 'jane@example.com'),
);
}
Delivery & Reliability Behavior
- Platform Security: Visitor tokens are bearer credentials persisted via
CerqleSessionStoreusing platform-native secure storage (flutter_secure_storage). - Authoritative Confirmation: Messages transition from
pendingtosentonly upon server receipt and ID issuance. - Network Failures & Unconfirmed State: If a request disconnects or times out before receiving a response, the message is marked
unconfirmedrather than failed, avoiding duplicate message sends. - Realtime Sync: A private Pusher channel delivers messages, typing changes, handoff updates, and launcher unread state while a controller has listeners. It disconnects in the background, then refreshes missed messages and restores realtime updates when the app resumes. Pull-to-refresh remains available as a user-triggered consistency check, and full initial history is loaded through bounded pagination; neither path runs on a timer.
- Safe Diagnostics: Diagnostic callbacks emit strictly redacted operational telemetry (durations, error codes, HTTP statuses) without logging PII, bearer tokens, or message content.
Development & Testing
# Get dependencies
flutter pub get
# Format code
dart format --output=none --set-exit-if-changed .
# Run static analysis
flutter analyze
# Run unit tests
flutter test
License
This project is licensed under the MIT License - see the LICENSE file for details.
Libraries
- cerqle_chat
- Ready-made and headless Flutter integrations for Cerqle visitor chat.