Termii Resolve Live Chat Flutter SDK
Termii Resolve Live Chat SDK for Flutter. Add Resolve in-app customer support to any Flutter app in a few lines.
All chat UI, messaging, and real-time transport are hosted by Resolve, so fixes and new features reach your users without an SDK update.
Features
- 💬 Real-time messaging over WebSocket
- 📱 iOS and Android
- 👤 Anonymous and identified users
- 📎 File attachments (images, documents, videos) and emoji picker
- 🎨 Theming and branding from the Resolve page in your Termii dashboard
- 🔔 Unread counts while the chat is closed
- ⚡ Warm start — reopening the chat is instant
Quick Start
dependencies:
termii_resolve_livechat: ^0.2.0
import 'package:termii_resolve_livechat/termii_resolve_livechat.dart';
await ResolveLiveChat.open(context, widgetId: 'YOUR_WIDGET_ID');
That pushes a full-screen chat route and pops it when the user taps the
chat's close button. Your widgetId comes from the Resolve page in your
Termii dashboard.
Platform Setup
Android — minimum SDK 21. Add the INTERNET permission to
android/app/src/main/AndroidManifest.xml (debug builds get it from
Flutter's debug manifest, release builds need it here):
<uses-permission android:name="android.permission.INTERNET" />
iOS — nothing to add; the chat page is served over HTTPS.
Identifying Users
Anonymous visitors can chat immediately. Pass a ResolveUser to link the
conversation to a profile:
await ResolveLiveChat.open(
context,
widgetId: 'YOUR_WIDGET_ID',
user: ResolveUser(
email: 'jane@example.com', // required
name: 'Jane Doe',
phone: '+2348012345678',
),
);
To identify someone after the chat is already open (e.g. they log in mid-conversation), use a controller:
controller.updateUser(ResolveUser(email: 'jane@example.com'));
Identity persists in the WebView's storage: clearing app data resets anonymous visitors, while identified users are re-linked by email.
Unread Counts and Notifications
Unread counts accrue while the chat is not visible — when it is visible, the page marks incoming messages read. Observe them at app level, so the callback outlives the chat screen:
ResolveLiveChat.preload(
widgetId: 'YOUR_WIDGET_ID',
user: user,
onUnreadCountChanged: (count) => setState(() => _badge = count),
);
This is the callback that fires while the chat is closed, which is when a badge
matters. ResolveChatWidget.onUnreadCountChanged only fires while that widget is
mounted — right for a chat you keep alive behind a tab, wrong for a badge on a
screen the chat is pushed over.
The SDK mirrors app background/foreground into chat visibility automatically
(manageAppLifecycle: true). If you hide the chat some other way while keeping
it mounted, mirror it yourself:
controller.setChatVisibility(false); // hidden → replies count as unread
controller.setChatVisibility(true); // visible → badge clears
Playing a sound or showing a banner
The chat page has its own notification chime, but it can only play while its WebView is on screen — once the chat screen is dismissed the WebView is detached and the platform will not play its audio. To alert the user, raise a local notification from the unread callback above. See example/lib/main.dart for a worked version using flutter_local_notifications.
This covers the chat being closed while the app is open. It cannot cover a backgrounded or terminated app: the OS suspends the WebView and its socket, so no event fires and there is nothing to notify from. That needs backend push support, which is planned.
Warm Start
The chat WebView is cached per widget ID and outlives the screen showing it, so closing the chat only hides it: reopening is instant and keeps the conversation, scroll position and socket. Preload it (typically after login) so the first open is instant too:
ResolveLiveChat.preload(widgetId: 'YOUR_WIDGET_ID', user: user);
Clear it when the user signs out:
ResolveLiveChat.logout();
A cached session is bound to the identity it was created for, so opening the
chat as a different user rebuilds it — one user can never see another's
conversation. logout is still worth calling: it removes the conversation at
sign-out rather than at the next open, frees the WebView's memory and socket,
and is the only way to clear a session when the next user is anonymous.
Pass keepAlive: false to open or ResolveChatWidget for a throwaway WebView
destroyed with the screen.
One WebView can only be displayed in one place, so if you ever show two chat screens at once the second loads its own copy instead of sharing the warm one.
Custom Layouts
For tabs, split views, or a chat you keep mounted, use the widget directly:
ResolveChatWidget(
widgetId: 'YOUR_WIDGET_ID',
user: ResolveUser(email: 'jane@example.com'),
onChatClosed: () => Navigator.of(context).pop(),
onError: (event) => log('chat failed: ${event.errorCode}'),
)
It fills whatever constraints it is given. While the page boots it shows
ResolveChatSkeleton, a placeholder of the chat's own layout; override it with
loadingBuilder.
File Attachments (Android)
iOS presents its file picker natively. Android WebViews delegate picking to the
host app — provide onAttachFile and return local file URIs, e.g. with
file_picker:
ResolveChatWidget(
widgetId: '...',
onAttachFile: (request) async {
final result = await FilePicker.platform.pickFiles(
allowMultiple: request.allowMultiple,
);
return result?.files.map((f) => Uri.file(f.path!).toString()).toList() ?? [];
},
)
Links Inside the Chat
Navigation away from the chat page is blocked. To open external links, handle
onExternalUrl with url_launcher:
onExternalUrl: (url) => launchUrl(url, mode: LaunchMode.externalApplication),
API Reference
ResolveLiveChat
open(context, {widgetId, user, controller, onError, onAttachFile, onExternalUrl, baseUrl, useSafeArea, keepAlive})— push a full-screen chat route; pops itself when the user taps closeuseSafeArea(bool, defaultfalse) — inset within the status bar and home indicator instead of filling the screenkeepAlive(bool, defaulttrue) — reuse the cached WebView across opens
preload({widgetId, user, baseUrl, onUnreadCountChanged})— load the chat in the background so the first open is instant; keeps it hidden so unread accrueslogout({widgetId})— clear the signed-in user's chat; call from sign-outrelease({widgetId})— same operation aslogout, named for reclaiming memory
ResolveChatWidget
| Parameter | Description |
|---|---|
widgetId (required) |
Widget ID from the Resolve page in your Termii dashboard |
user |
Identify the user as the page loads |
controller |
ResolveChatController for visibility, user updates, reload |
baseUrl |
Override the widget host (self-hosted or staging) |
onReady |
Chat UI finished mounting inside the page |
onChatClosed |
User tapped the chat's own close control |
onUnreadCountChanged |
Unread count changed. Fires only while mounted — for badges use ResolveLiveChat.preload |
onError |
Chat failed to initialize (bad widget ID, config fetch failure) |
onEvent |
Every bridge event, including ones this version doesn't map |
onAttachFile |
Android only: handle attachment picking |
onExternalUrl |
A link points outside the widget host |
manageAppLifecycle (default true) |
Mirror app lifecycle into chat visibility |
keepAlive (default true) |
Reuse the cached WebView instead of reloading |
loadingBuilder |
Replace the default ResolveChatSkeleton placeholder |
readyTimeout (default 10s) |
Reveal the page even if it never reports ready |
ResolveChatController
setChatVisibility(bool)— show or hide the chatupdateUser(ResolveUser)— identify the user after the page has loadedreload()— reload the chat page
ResolveChatSession
The cached WebView behind a widget ID. ResolveLiveChat.preload / logout cover
the common cases; reach for this to observe events with no chat screen mounted.
addListener/removeListener— receive bridge events while the chat is closedsetVisibility(bool),identify(ResolveUser),reload()release({widgetId})(static) — destroy cached sessions
ResolveUser
email(string, required) — user's email address; identity is keyed on thisname(string, optional)phone(string, optional) — international format, e.g.+2348012345678
ResolveChatEvent
Bridge events delivered to onEvent, with type one of ready, chatClosed,
unreadCountChanged, error, unknown. Accessors: unreadCount, errorCode,
errorMessage.
Example
See example/ — run with your own widget ID:
cd example
flutter run --dart-define=RESOLVE_WIDGET_ID=YOUR_WIDGET_ID
The example demonstrates preloading, unread badges, local notifications, and clearing the session on sign-out.
Documentation
- 📖 Integration Guide — detailed integration examples
Support
- 📧 Email: support@termii.com
- 💬 Live Chat: Visit termii.com/products/resolve
- 🐛 Issues: GitHub Issues
Built with ❤️ by Termii
Libraries
- termii_resolve_livechat
- Termii Resolve Live Chat SDK for Flutter.