Skip to main content
Search

Mobile app chat

Open the support chat full screen inside your Flutter app, in the app's language, with users signed in by phone number.

Your mobile app can show the support chat full screen with our ready Flutter package. App users talk to the same AI assistant, knowledge base and agents as visitors of your website, the chat speaks the language of the app, and signed-in users enter it with just their phone number.

What the package does

  • Shows the chat full screen on phones and tablets, as its own screen or as a tab.
  • Takes the language of the chat from the app, not from the phone settings.
  • Signs in app users with a token from your server, so the assistant can answer personal questions.
  • Lets users attach photos and files from the camera, the gallery or the file system.
  • Lets users record voice messages with the microphone button, where voice messages are available.
  • Opens links from answers in an in-app browser, so the chat stays open underneath.
  • Closes the chat with the Android back button and tells your app about it.
  • Reports the number of unread replies for a badge on your chat button or tab.
  • Keeps the conversation of a user who is not signed in, and clears the chat when a user signs out or another user signs in.
  • Shows an error text with a Try again button instead of a blank screen.

The package does not deliver push notifications about new replies.

Get the package

We hand over the package when you connect your app: write to support@epavdesk.com. The current version is 0.4.0. Your developer receives three values:

  • your support address, for example https://support.example.com;
  • the ID of your live chat inbox, the same as in the website code;
  • the address of the chat page.

The app needs Flutter 3.35 or newer, iOS 15.4 or newer, and on Android an up to date Android System WebView from Google Play.

Add the chat screen

import 'package:support_chat/support_chat.dart';

SupportChatView(
  baseUrl: Uri.parse('https://support.example.com'), // your support address
  inboxId: 'YOUR_INBOX_ID',                            // the live chat inbox ID
  pageBase: 'CHAT_PAGE_ADDRESS',                       // the chat page address we give you
  appVersion: 'com.example.app/3.4.1',                 // optional
  userToken: () => backend.supportChatToken(),         // a JWT from your server, or null
  locale: () => appLocale,                             // optional, the app language
  onClose: () => Navigator.of(context).pop(),          // optional, the Android back button
  onUnreadChanged: (count) => badge.value = count,     // optional, unread replies
  onError: (error) => reportError(error),              // optional
)

SupportChatView is a complete screen: open it as a route or put it in a tab. It has no chat button of its own, your app decides where the chat opens. The chat page itself lives on our side, so its look, texts and behavior can change without a release of your app; a store release is needed only when you move to a new version of the package. appVersion sends nothing but the version of your app: it lets the designer tell how many installs will see a change. The page address carries your support address, the inbox ID, the language, version numbers and the platform, nothing that identifies a person.

The back button and unread replies

On Android, while the chat is on screen and onClose is set, the system back button goes to the chat first: the chat tidies up, for example stops a voice recording, and the package calls onClose. If the chat does not answer within half a second, onClose is called anyway. Your app closes the chat itself: pop the route, or switch to another tab if the chat is a tab. Without onClose, while the chat is loading or shows an error, and on iOS, the back button and the back swipe work as on any other screen.

onUnreadChanged gets the number of unread replies each time it changes, so your app can show a badge on its chat button or tab. It is counted only while the chat screen exists: a closed route counts nothing, while a chat tab kept alive in the background keeps counting.

The Domain list

The chat page in the app comes from its own address, the address of the chat page, not from your support address. Your platform always lets that page through, so nothing has to be added for the app, whether the Domain list of the live chat inbox (Setup > Security) is empty or filled. The list only limits which websites may show your website chat. See Live chat widget.

Sign in app users

The package calls your userToken callback every time the chat opens. The callback returns a JWT from your server, or null for a user who is not signed in. Your server signs the token with the Secret key of the live chat inbox (Setup > Security), exactly as for the website. The key must never be inside the app.

A phone number is enough to sign in:

import time
import jwt  # PyJWT

token = jwt.encode(
    {"external_user_id": str(user.id), "phone_number": user.phone, "exp": int(time.time()) + 600},
    INBOX_SECRET,
    algorithm="HS256",
)
  • external_user_id (up to 128 characters) and exp are required. A lifetime of 5 to 15 minutes is enough: after the exchange the chat keeps its own session.
  • phone_number (up to 20 characters), first_name, last_name and contact_custom_attributes are optional. A contact without a name is shown to agents under the phone number.
  • Add email only if your app has confirmed it: the sign-in is then linked to an existing contact with that email and its history.

If the platform rejects a token, the package asks your callback once more for a fresh one and reports an error only after that. A signed-in app user counts as verified, so custom tools marked Require verified contact can answer personal questions, for example about an order or a loan: see Custom tools. All token fields are described in Live chat widget.

Sign out

When a user signs out of your app, call:

await SupportChat.logout();

The chat session survives app restarts, so without this call the next person on the phone could see the previous user's conversations. If the call is missing, the package still starts a clean session when the token belongs to another user, or when an anonymous user follows a signed-in one. A user who is not signed in finds their conversation again each time they open the chat, and a user who chatted anonymously and then signs in keeps that history in their account.

Language

The chat's interface follows the language you return from the locale callback; without the callback it takes the app's current localization. This works while Language of the live chat inbox, tab General, is Auto-detect (browser language): a fixed language there applies to everyone. The chat's interface texts exist in English, Estonian, Russian, Ukrainian, German, French, Spanish, Italian and Brazilian Portuguese, among others; for another app language the chat takes the web view's language if it has that one, otherwise the inbox's Fallback language. The package's own menus and error texts exist in English, Russian and Ukrainian, other languages get English.

The assistant replies in the customer's language unless its Languages list holds exactly one language: see Assistant settings.

  • On Android the paperclip opens a menu with Take photo, Photo library and Choose file. On iOS the system shows its own choice.
  • iOS needs these texts in Info.plist, written for your users:
<key>NSCameraUsageDescription</key>
<string>To send a photo to support.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>To send an image to support.</string>
<key>NSMicrophoneUsageDescription</key>
<string>To record a voice message for support.</string>
  • Android needs the INTERNET permission and <queries> entries for links, email, phone calls and the browser in AndroidManifest.xml; the example app of the package shows them. If your app declares the CAMERA permission, it must be granted before the user takes a photo, and the package does not ask for it: your app does.
  • Links from answers and attachments open in an in-app browser, so the chat stays where it was. Email and phone links open in the matching apps.

Voice messages

The microphone button appears in the app when speech recognition is set up, files are allowed in the chat and Voice messages is not switched off on the designer's App tab. The package asks the phone for permission at the first tap. On Android add:

<uses-permission android:name="android.permission.RECORD_AUDIO"/>

Without this line the chat works, but recording does not start and a hint suggests typing. On iOS the NSMicrophoneUsageDescription text above is enough. Voice messages need version 0.3.0 of the package or newer. More in Voice messages.

The look of the chat in the app

The look is set in the live chat inbox, tab Designer, tab App. It is kept apart from the website on purpose: a change for the website never alters an app that is already in the stores.

  • Changes on the App tab wait until you press Apply to the app (or Discard). Then press Save on the settings page, and the app picks them up.
  • Check on a device gives a link to open on a phone. For half an hour it shows your draft, not what users see now.
  • Font and Vibration on a reply need a newer package than the current 0.4.0, so current apps ignore them. The designer warns about such settings and shows what share of installs will see the change.

When something goes wrong

The chat shows a short message with a Try again button, and your onError callback gets a code:

Code When
invalidConfig The support address is not a bare https address such as https://support.example.com, or the inbox ID is not valid.
network No connection, the platform answered with a server error, or the chat page did not load. httpStatus holds the status code when the platform answered.
inboxUnavailable The inbox is turned off or deleted. httpStatus holds the status code of the platform's answer.
authFailed The callback failed or returned a token without external_user_id, or the platform rejected the token twice.
timeout The chat did not load within 30 seconds.
webViewFailed The web view could not be created.

Version 0.4.0 removed the code sessionResetFailed, which no longer occurred: when you update the package, delete it from your error handling.

Was this article helpful?