Skip to content

Flutter

Your Help button opens a native sheet. If the app has help articles, the assistant answers from them. Otherwise, or when the user asks, the contact form opens: the user writes, sees a screenshot of the screen they were on, hides or removes anything, and presses Send. The message becomes a conversation in your inbox.

Install

Not published yet

The package isn’t on pub.dev yet: it comes with your account, under the name below.

dependencies:
  sanad_support: ^0.1.0

On Android, the app needs the INTERNET permission, as any networked app does.

Use it

void main() {
  AppSupport.configure(const AppSupportOptions(
    appKey: 'pk_…',                          // Support apps → keys (publishable)
    apiUrl: 'https://<your support host>',
    appVersion: '4.2.0',
  ));
  runApp(const AppSupportScope(child: MyApp())); // above MaterialApp, so the capture sees every route
}

// your own help button:
IconButton(icon: const Icon(Icons.help_outline), onPressed: () => AppSupport.open(context));
  • Wrap the app once in AppSupportScope, above MaterialApp. Without it, Support still opens, but with no screenshot.
  • Mark what must never leave the device with SupportMask(child: …) (card numbers, balances, personal details). Masked widgets, obscured text fields (obscureText: true) and platform views (WebViews, maps) are blacked out before the user sees the preview.
  • The language follows AppSupportOptions.language, then the app’s locale (ar or en), then the app’s first language. Arabic is fully right to left.

Conversations, messages from your team, and call-backs

// after sign-in: your own opaque id for the user (never an email, a name or a device id)
await AppSupport.identify('u-8812');
// on sign-out
await AppSupport.identify(null);

// the user's conversations with your team, or one of them
AppSupport.openConversations(context);
AppSupport.openConversation(context, conversationId);

// look for a message from your team now (the SDK also does it on its own, below)
final shown = await AppSupport.checkMessages(context);

// optional: name your root navigator, where your team's message is shown
runApp(AppSupportScope(navigatorKey: navigatorKey, child: MyApp(navigatorKey: navigatorKey)));
  • Two-way conversations. Every message the user sends becomes a conversation they can follow in the app: the list (newest first, open or closed), and each thread with each message labelled: a person on your team, the assistant (automated), or a notice. The user replies from the thread; replying reopens a closed conversation. Open screens refresh every 15 seconds while the app is in the foreground, and pull to refresh works any time.
  • A message from your team, shown once. When a proactive rule opens a conversation with the user, the SDK shows a small banner, “We noticed a problem”, over your screen without blocking it. It opens the thread, and closes by itself after 20 seconds. The SDK looks when the app starts, when it comes back to the foreground, and after identify(). With no identified user, nothing is looked up at all.
  • The identified user goes with every message and call-back, and lets your team reach them in the app. Conversations are bound to the install: another install never sees them, whatever user id it names.
  • Call-back. From the form, “Request a call-back” asks for the number with its country code (Arabic digits work), what the call is about, and an optional preferred time. It is never queued offline: a phone number is not written to storage.
  • A person stays in reach. “Talk to a person” is always under the assistant’s answers.

The assistant runs a flow

When the support app lists chatbot flows for its assistant, and a question maps to one of them, the assistant offers it: “I can help you do that here”, with a button that starts it. The flow runs in the sheet, in native widgets: options and cards, yes and no, and forms.

AppSupport.configure(AppSupportOptions(
  appKey: 'pk_…',
  apiUrl: 'https://<your support host>',
  userToken: await myServer.flowToken(),  // optional: the signed-in user (HS256 JWT, ≤ 1 hour)
  refreshUserToken: myServer.flowToken,   // called once when it expires
));

Without a user token, a flow can talk and let the user choose, but changes nothing for the user. If the flow isn’t available, the sheet goes back to the assistant as if no flow had matched.

What it does, and what it never does

  • It never opens itself and never throws into your app. open() completes quietly; problems go to debugPrint. A second tap while Support is opening or open does nothing. AppSupport.close() closes it.
  • One still image of your app, taken the moment Support opens, before the sheet is drawn. It needs no permission and has no system prompt. It never shows other apps, the status bar or the keyboard. The user can hide parts (burned into the pixels), remove it, or attach it. What the preview shows is exactly what is uploaded.
  • The image lives in memory only. It is never written to disk. When the service is unreachable, the message (text only) is queued: at most 5 messages, for up to 3 days, sent at the next configure() or open(), or when the app comes back to the foreground. The form tells the user the message will go without the screenshot.
  • Only the allowlisted context is sent: app version, build, OS name and major version, phone or tablet, and the language. The form lists it under the message.
  • A random install id is used for rate limits, and to keep a user’s conversations to the install they were written from.

Your privacy policy: a template

When you contact support in this app, we send your message, and, if you leave them attached, a screenshot of the screen you were on and your conversation with our help assistant. We also send the app version, the operating system and the device type. You see all of it before you press Send. If you are signed in, your account id goes with it, so our replies and our messages to you (for example, when we notice a problem that affected you) reach you in the app. If you ask us to call you, we send the number you give and what the call is about. Support messages are kept for 180 days after they are resolved.