Rekhu Assistant
Rekhu Assistant: Flutter
Put the assistant in a Flutter app with rekhu_assistant, on the chat route of your Laravel or Node.js back end.
What this package is for
rekhu_assistant puts the assistant in your Flutter app, on iOS, Android, web, macOS, Windows and Linux. The app talks to your own back end, where rekhu/assistant-laravel or @rekhu/assistant-node knows the user, their actions, pages and help, and calls Rekhu. The app never holds a Rekhu key.
The package gives you three levels:
| Class | What it is |
|---|---|
RekhuChat |
a ready chat screen, in a page or a bottom sheet, in English, Arabic (right to left) and Dutch, or in your own words |
RekhuChatController |
the chat as state, for a chat screen of your own design |
RekhuAssistantClient |
the connection itself, as a stream of events |
Its one dependency is http. It needs Dart 3.5 or newer.
Set up in four steps
- Install the server package on your back end and add its secret key; see Laravel or Node.js.
- Put the chat route behind your API's sign-in. A mobile app signs in with a token, not a cookie.
- Add the package to your Flutter app.
- Open the chat with a client pointed at the chat route, sending the user's token.
In Laravel, register the routes in routes/api.php, and set REKHU_ASSISTANT_ROUTES=false in .env so the package does not also register its web routes. In Node.js, mount the handler behind the middleware that checks your token:
// routes/api.php
use Rekhu\Assistant\RekhuAssistant;
RekhuAssistant::routes(['middleware' => ['api', 'auth:sanctum']]);
// Express: your token check first, then the assistant
app.use('/api/rekhu', requireToken, assistant.express());
Then add the package to the app:
flutter pub add rekhu_assistant
Open the chat
Keep one client for as long as the user is signed in, so the conversation goes on when the chat opens again:
import 'package:rekhu_assistant/rekhu_assistant.dart';
final client = RekhuAssistantClient(
endpoint: Uri.parse('https://api.example.com/api/rekhu/assistant/chat'),
headers: () async => {'Authorization': 'Bearer ${await auth.token()}'},
locale: 'en-GB',
);
// a button that opens the chat over the current screen
FloatingActionButton(
onPressed: () => RekhuChat.show(context, client: client, onOpenPage: (url) => context.go(url)),
child: const Icon(Icons.chat_bubble_outline),
);
// or the chat as a screen of its own
Scaffold(body: SafeArea(child: RekhuChat(client: client, onOpenPage: (url) => context.go(url))));
headers is called before every call, so a refreshed token is always used. When your back end answers 401 or 419, the chat says the session ended and RekhuChatController.signedOut is true, so the app can sign the user in again.
Pages and actions
- Page cards carry an address from your back end: an app path such as
/orders/1001, or a full URL.onOpenPageopens it with your router, or withurl_launcherfor a website. Links in answers go there too. - Actions run on your back end only after the user taps Confirm on the card. When one ran,
onActionis called with its card: refresh what the screens show.
Languages
locale (such as en-GB, ar-SA or nl-NL) is the language the user reads. Answers come in it, and the chat's own words are English, Arabic or Dutch accordingly; any other language gets English unless you give texts. Arabic, Hebrew, Persian and Urdu read right to left, whatever your app's own locale.
Keep the conversation across app starts
By default the conversation lasts while the app runs. To keep it across starts, give the client a store:
class PrefsStore implements RekhuStore {
PrefsStore(this.prefs);
final SharedPreferences prefs;
@override
Future<String?> read(String key) async => prefs.getString(key);
@override
Future<void> write(String key, String? value) async => value == null ? prefs.remove(key) : prefs.setString(key, value);
}
final client = RekhuAssistantClient(endpoint: endpoint, headers: headers, store: PrefsStore(await SharedPreferences.getInstance()));
A signed-in user also finds earlier conversations in the list next to the chat. See Conversation history.
A public chat for visitors
For people who are not signed in, point the client at the public route (the chat's address with visitor/ before chat) and set visitor. The client sends an anonymous id per install as X-Rekhu-Visitor.
final client = RekhuAssistantClient(
endpoint: Uri.parse('https://api.example.com/api/rekhu/assistant/visitor/chat'),
visitor: true,
);
In Laravel, turn the public chat on with REKHU_ASSISTANT_GUESTS=true and give its route your API middleware with REKHU_ASSISTANT_GUEST_MIDDLEWARE=api. A 404 means the public chat is off or the address misses api/; a 419 means the route still has the web middleware; a 422 means the client was made without visitor: true.
Make it look like your app
The chat follows your app's Theme. Change what you want with RekhuChatTheme, and turn off the chat's own bar when your screen has an app bar:
RekhuChat(
client: client,
theme: const RekhuChatTheme(accent: Color(0xFF0F766E), radius: 18),
header: false,
)
greeting, quick and texts (RekhuTexts.en.copyWith(...)) replace the server's first message, its quick questions and the chat's own words.
A chat of your own design
RekhuChatController is the chat as state; draw it any way you like:
final chat = RekhuChatController(client: client, onAction: (card) => refresh());
await chat.load();
ListenableBuilder(
listenable: chat,
builder: (context, _) => ListView(children: [
for (final item in chat.items)
switch (item) {
RekhuUserItem(:final text) => MyUserBubble(text),
RekhuBotItem(:final text) => MyBotBubble(child: RekhuMarkdown(text, onLink: open)),
RekhuStepItem(:final label) => MyStep(label, done: item.over),
RekhuCardItem() => MyCard(item, onConfirm: () => chat.confirm(item), onCancel: () => chat.cancel(item)),
RekhuErrorItem(:final message) => MyError(message),
},
]),
);
chat.send('Where is my order?');
Or use the client's events directly: client.send(text) is a Stream<RekhuEvent> of RekhuStart, RekhuText, RekhuStep, RekhuCard, RekhuError and RekhuDone.
Warning: never put a secret key (
agk_…) in a mobile app; anyone can read it from the build. The app only sends the user's own token, and the id of a confirm card the server issued to this user.
Next
- Conversation history: the list, and the calls behind it.
- Actions and the scan: what the assistant may do for your app's users.