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() ?? [];
  },
)

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 close
    • useSafeArea (bool, default false) — inset within the status bar and home indicator instead of filling the screen
    • keepAlive (bool, default true) — 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 accrues
  • logout({widgetId}) — clear the signed-in user's chat; call from sign-out
  • release({widgetId}) — same operation as logout, 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 chat
  • updateUser(ResolveUser) — identify the user after the page has loaded
  • reload() — 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 closed
  • setVisibility(bool), identify(ResolveUser), reload()
  • release({widgetId}) (static) — destroy cached sessions

ResolveUser

  • email (string, required) — user's email address; identity is keyed on this
  • name (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

Support


Built with ❤️ by Termii

Libraries

termii_resolve_livechat
Termii Resolve Live Chat SDK for Flutter.