Docs menu

Flutter

kavchat_flutter adds the scripted support chat to your Flutter app: buttons, free text matching, a contact form that opens a request, an offline copy of the published bot, and right-to-left layout for Hebrew and Arabic.

Install

pubspec.yaml
dependencies:
  kavchat_flutter: ^0.0.11

The package brings http, shared_preferences (release cache and saved conversation) and url_launcher (links in your scripts). For tel: links on Android 11 and later, add a <queries> entry for the tel scheme to AndroidManifest.xml, and one for https for web links (see the url_launcher README). iOS needs LSApplicationQueriesSchemes only if you call canLaunchUrl yourself.

Usage

Dart
final kavchat = KavchatController(
  publicKey: 'pk_live_...',
  // apiBase: 'https://api.kavchat.com', // default
  // locale: 'he',                        // default: device language if translated, else the bot language
  onScreen: (screen, targetId) => navigatorKey.currentState?.pushNamed('/$screen', arguments: targetId),
);

Scaffold(
  floatingActionButton: KavchatLauncher(controller: kavchat), // hidden while the bot is off
);

// or open it yourself
showKavchat(context, kavchat);

// or embed the screen in your own route
KavchatChat(controller: kavchat, onClose: () => Navigator.pop(context));

Create the controller once (for example in a State or a provider) and dispose() it with its owner.

KavchatLauncher is a FloatingActionButton. Place it with Scaffold.floatingActionButtonLocation. The bot setting kavchat.settings?.position (start or end) tells you which side was chosen in the dashboard.

When the dashboard sets a message next to the chat button (a speech bubble, curved text above the button, the button itself stretching out to talk, or a line rising from the button with the bot's first options), KavchatLauncher shows it after the configured delay, at most once a day: closing it or opening the chat hides it until tomorrow. Tapping an option on the line opens the chat with that option chosen, as if tapped inside it. It is never shown while the chat is open, and it does not move when the system asks for reduced motion. With "Don't show on phones" it shows only on tablets (shortest side 600 dp or more). On phones the line shows up to two options. KavchatLauncher(controller: kavchat, attention: false) turns it off.

When the theme's launcher icon is the face, it blinks now and then while the button is idle, but not while the chat is open, while the app is in the background, or when the system asks for reduced motion.

The bot is not shown (the launcher renders nothing, and KavchatChat shows "not available") when the release is enabled: false: the bot is off, it is limited to testers and the user is not one, or the quota is used up (reason: 'quota').

Identity token

For signed-in users, sign a short JWT on your server with the secret key (never in the app, see Identify users) and pass it:

Dart
KavchatController(publicKey: 'pk_live_...', userToken: jwt, userName: 'Dana');
await kavchat.setUser(token: newJwt, name: 'Dana'); // after login
await kavchat.setUser(token: null);                 // after logout

It is sent as Authorization: Bearer <token> on every call. With a token the contact form does not require an email or a phone, and the testers audience can recognize the user. setUser reloads the release, and a different user starts a new conversation, so a conversation saved on the device never carries over to another account.

Links, screens and the contact form

  • Buttons of type link open with url_launcher, only for https: and tel:. Links inside bot messages: https: only.
  • Buttons of type screen call onScreen(screen, targetId). Without a callback they do nothing.
  • "Talk to our team" opens the built-in form (name, email, phone, topic, message, with the conversation attached) and shows the request number on success.

The screen list in the editor

Declare the screens your app can open, once in KavchatController(screens: ...). The SDK sends the list to Kavchat by itself, and the script editor then offers these screens in a dropdown with their names, instead of a free text field. It also asks for an item id when a screen needs one, and warns about a button that points to a screen you did not declare. Nobody types the screen list by hand in the dashboard.

Dart
final kavchat = KavchatController(
  publicKey: 'pk_live_...',
  screens: const [
    KavchatScreen(id: 'orders', label: 'My orders'),
    KavchatScreen(id: 'product', label: 'Product page', needsTarget: true), // the button carries a targetId
  ],
  onScreen: (screen, targetId) => navigatorKey.currentState?.pushNamed('/$screen', arguments: targetId),
);
// later, to replace the list: await kavchat.registerScreens([...]);
  • id is the value onScreen receives: letters, digits, _, . and -, up to 40 characters. label is the name the admin sees, up to 60 characters. Up to 100 screens. Invalid entries and duplicate ids are dropped.
  • needsTarget: the button also carries an item id (for example a product id), and onScreen receives it as targetId.
  • The list is sent in the background after the release loads, only when it changed or once a week. A failure is silent and retried at the next launch. The user never sees anything.
  • The registered screens appear in the dashboard under Bot, Settings, "App screens", with the platforms that reported each one. A screen you removed from the app can be deleted there.

RTL

The chat sets its own Directionality from the UI language: he and ar are right to left, everything else left to right. It does not change your app's direction. Each bubble also picks its direction from its first strong letter, so mixed content reads correctly. The UI language and the script language are separate: an English bot can show Arabic buttons such as "Back" when locale: 'ar'.

Theming and branding

Colors come from the bot setting primaryColor. Text on it is black or white by WCAG contrast, and a very light color is replaced by a dark one where it is used as text. Override:

Dart
KavchatLauncher(controller: kavchat, theme: KavchatThemeData.fromColor(const Color(0xFF0F766E)));
KavchatChat(controller: kavchat, theme: KavchatThemeData.fromHex('#0f766e').copyWith(background: Colors.white));

The "Powered by Kavchat by maxsave" badge appears on all plans, with a link to kavchat.com. On Premium, you can hide it in the bot settings.

Live chat with a representative

There is nothing to set up to get started. When live chat is turned on in the dashboard, the chat in your app offers it exactly like the website widget.

  • The option: "Talk to a representative" appears before "Talk to our team", only after the server confirms that a representative is free. Availability is checked again when the button is tapped. Outside business hours, or when everyone is busy, only the contact form is offered, with a short line saying why, and when the team is back if that is known. When the monthly quota is used up, the form is shown without an explanation.
  • The request: guests leave an email or a phone, in case nobody is free in time, and can add a name and a first message. When more than one department is open, they choose one. Signed-in users (userToken) do not fill in contact details. If nobody can take the chat, the contact form opens with what the user already typed.
  • Waiting: "Waiting for a representative" with a countdown to the end of the wait time you set, and the buttons "Leave a message instead" and "End chat". Users can write while they wait. When the time is up the chat becomes a request automatically, and the user sees that nobody was free in time and the request number.
  • The chat: the representative's name, messages, a typing indicator, transfers to another representative, and ending the chat. Everything from the server is shown as plain text, with no links.
  • Real time: a Server-Sent Events connection over a streamed http response. If it drops, the chat reconnects with growing delays and catches up on everything it missed, so no message is lost or shown twice. On networks that block this connection, and on Flutter Web, it uses repeated requests (long-poll). Every message has an id, so a retry never creates a duplicate. A message that was not sent is marked, and a tap sends it again.
  • Background and restart: when the app goes to the background (paused, hidden) the connection is closed, and when it is resumed the chat catches up. The chat access credential is valid for up to 12 hours or until the chat ends, and is kept in the storage you choose, so after the app restarts an ongoing chat comes back on screen. It is never put in a URL or a log.
  • No push notifications in this version: messages from the representative arrive only while the app is open.

Keep the credential in secure storage (Keychain on iOS, Keystore on Android), for example with flutter_secure_storage. Without secureStorage it is kept in the same storage as the conversation. A button of your own opens the request form when a representative is free, and otherwise the contact form with the explanation:

Dart
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class SecureKavchatStorage implements KavchatStorage {
  final _s = const FlutterSecureStorage();
  @override
  Future<String?> read(String key) => _s.read(key: key);
  @override
  Future<void> write(String key, String value) => _s.write(key: key, value: value);
  @override
  Future<void> remove(String key) => _s.delete(key: key);
}

final kavchat = KavchatController(
  publicKey: 'pk_live_...',
  secureStorage: SecureKavchatStorage(),
  livePage: 'checkout',
  onLivePhase: (phase) => debugPrint('live chat: $phase'),
);

// your own button
FilledButton(onPressed: kavchat.openLive, child: const Text('Chat with us'));

The same logic, without Flutter, is in kavchat_engine (KavchatClient, LiveSession, and the shared state machine liveApplyEvent, liveLines, liveStatus, which passes the same test vectors as the website widget).

Questions, conditions and leads

No app configuration is needed. A script can ask for details (a name, phone number, email, number or text), use conditions to decide what happens next (Premium) and send a lead. Everything runs on the device.

  • Matching keyboard: When the bot is waiting for an answer, the text field opens a phone, email or numeric keyboard with autocomplete. If the answer is invalid, the bot explains what is missing and the field is highlighted. Optional questions show a "Skip" button.
  • Lead: When consent text is present, "Send" and "No, thanks" buttons appear. If there is no network connection, the lead is saved on the device and sent when the connection returns, without duplicates.
  • Prefill: Answers the user has already provided fill in the contact form and the live chat request form, and are attached to the request and the live chat. The name, email and phone number from the identity token count as known answers, so the bot does not ask for them again.
  • Current screen: livePage also identifies the screen that conditions check and that is sent with leads. When the user moves to another screen, call setPage('checkout').

AI answers (Premium)

There is nothing to set up in your app. When AI answers are turned on in the dashboard (on your own AI provider key), a typed question that no script matches is sent to Kavchat, and the chat shows a typing indicator while it waits, for up to 20 seconds. The answer is shown as plain text with a small "AI answer" label in the user's language, followed by a button for a related topic when there is one, and "Talk to our team".

If there is no answer, or the request fails, takes too long or hits the limit (10 answers per conversation every 15 minutes), the chat shows its regular "no match" reply. The question still appears in the unanswered questions report, and AI answers are kept in the saved conversation like any other message.

Offline and caching

The release is cached with its ETag (If-None-Match, 304). With no network or a server error, the cached copy is used and a banner says so. The conversation is saved per bot and restored for 24 hours on the same release. The path is reported to the server 2 seconds after the last action and when the app goes to the background.