Flutter Chatbot SDK
dialogs_chatbot_flutter is a Flutter plugin that embeds a fully functional EnableX
Dialogs chatbot into any iOS or Android application. It's built on a
WebView-Bridge architecture — the chat protocol runs inside a bundled
HTML/JS page, and Dart communicates with it through a bidirectional JavaScript bridge. Zero
native Swift/Kotlin code is required — the plugin is framework-agnostic and works with
Provider, Riverpod, BLoC, GetX, or plain setState.
First release of the Flutter Chatbot SDK, published on pub.dev. See Release Notes for what's included.
View on pub.devPrerequisites
Flutter & Dart
| Tool | Minimum Version |
|---|---|
| Flutter | 3.10.0 |
| Dart | 3.0.0 |
iOS
| Requirement | Value |
|---|---|
| Minimum iOS version | 12.0 |
NSCameraUsageDescription | Required in Info.plist for file/image upload |
NSMicrophoneUsageDescription | Required in Info.plist for audio/video calling |
NSPhotoLibraryUsageDescription | Required in Info.plist for gallery access |
Android
| Requirement | Value |
|---|---|
minSdkVersion | 21 (Android 5.0) |
CAMERA permission | Required in AndroidManifest.xml for file upload |
RECORD_AUDIO permission | Required for audio/video calling |
READ_EXTERNAL_STORAGE | Required for file picker (Android < 13) |
READ_MEDIA_IMAGES | Required for image picker (Android 13+) |
Installation
Add Dependency
In the host app's pubspec.yaml:
dependencies:
dialogs_chatbot_flutter: ^1.0.0
# Required peer dependency — add to host app
flutter_inappwebview: ^6.1.5
Run:
flutter pub get
iOS Setup
In ios/Runner/Info.plist, add:
<key>NSCameraUsageDescription</key>
<string>Camera access is required to send photos and make video calls.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Microphone access is required for audio and video calls.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Photo library access is required to send images.</string>
In ios/Podfile, ensure minimum iOS version:
platform :ios, '12.0'
Android Setup
In android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.INTERNET" />
In android/app/build.gradle:
android {
defaultConfig {
minSdkVersion 21
}
}
Pattern 1 — Simplest (Screen, no controller)
import 'package:dialogs_chatbot_flutter/dialogs_chatbot_flutter.dart';
Navigator.push(context, MaterialPageRoute(
builder: (_) => EnxChatScreen(
config: const EnxBotConfig(
botId: 'YOUR_BOT_ID',
host: 'https://dialogs.enablex.io',
),
),
));
Pattern 2 — Embedded Widget
Scaffold(
body: EnxChatWidget(
config: const EnxBotConfig(
botId: 'YOUR_BOT_ID',
host: 'https://dialogs.enablex.io',
),
theme: EnxChatTheme.mobileClassic,
onMessageReceived: (msg) => print(msg.text),
),
)
Pattern 3 — External Controller (full control)
class ChatPage extends StatefulWidget {
const ChatPage({super.key});
@override
State createState() => _ChatPageState();
}
class _ChatPageState extends State {
late final EnxChatController _controller;
@override
void initState() {
super.initState();
_controller = EnxChatController(
config: const EnxBotConfig(
botId: 'YOUR_BOT_ID',
host: 'https://dialogs.enablex.io',
callingEnabled: true,
),
);
_controller.onStatusChanged = (s) => setState(() {});
_controller.onError = (msg, type) => _showError(msg);
_controller.initialize();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => EnxChatWidget.withController(
controller: _controller,
showCallingOptions: true,
onAudioCall: () => /* your call SDK */,
onVideoCall: () => /* your call SDK */,
);
}
Pattern 4 — BLoC / Stream Integration
// In your BLoC:
final _sub = controller.messageStream.listen((msg) => add(BotMessageEvent(msg)));
final _statusSub = controller.statusStream.listen((s) => add(ConnectionEvent(s)));
// In dispose:
await _sub.cancel();
await _statusSub.cancel();
controller.dispose();
Configuration Reference
EnxBotConfig Fields
| Field | Type | Required | Description |
|---|---|---|---|
botId | String | Yes | Unique bot ID from EnableX dashboard |
host | String | Yes | Backend base URL |
rootPath | String | No | URL path prefix (default '') |
callingEnabled | bool | No | Show call buttons (default false) |
Theme Customisation
const EnxChatTheme myTheme = EnxChatTheme(
primaryColor: Color(0xFF1A73E8), // Google Blue
userBubbleColor: Color(0xFFE8F0FE),
botBubbleColor: Colors.white,
backgroundColor: Color(0xFFF5F5F5),
userAvatarColor: Color(0xFF1A73E8),
botAvatarColor: Color(0xFF34A853),
useMobileClassicLayout: false,
);
For a WhatsApp-style layout:
theme: EnxChatTheme.mobileClassic
Message Types — Developer Reference
When onMessageReceived fires, check message.type to handle each case:
controller.onMessageReceived = (EnxMessage msg) {
switch (msg.type) {
case EnxMessageType.text:
// msg.text — plain text
// msg.actions — optional action buttons
break;
case EnxMessageType.suggestion:
// msg.choices — List
// msg.suggestionType — single or multiple
break;
case EnxMessageType.image:
// msg.media?.url — CDN image URL
break;
case EnxMessageType.video:
// msg.media?.url — video URL
break;
case EnxMessageType.file:
// msg.media?.url, msg.media?.fileName, msg.media?.mimeType
break;
case EnxMessageType.system:
// msg.handoverStatus — handover lifecycle phase
break;
case EnxMessageType.typing:
// Handled internally — shows EnxTypingBubble
break;
default:
break;
}
};
Sending Messages Programmatically
// Plain text
await _controller.sendText('What are your hours?');
// Single suggestion chip (after user taps)
await _controller.sendChoice(EnxChoice(title: 'Yes', value: 'yes'));
// Multiple suggestion chips
await _controller.sendMultiChoice([
EnxChoice(title: 'Option A', value: 'a', isSelected: true),
EnxChoice(title: 'Option B', value: 'b', isSelected: true),
]);
// File upload
final bytes = await File(path).readAsBytes();
await _controller.sendFile(
bytes: bytes,
fileName: 'report.pdf',
mimeType: 'application/pdf',
);
// Reset conversation
await _controller.resetConversation();
Mode A — Bot-Initiated (Automatic)
The bot sends a message with audioVideoAction = "start" and iFrameUrl. The plugin automatically shows EnxVideoCallOverlay. No host app code needed.
Mode B — Button-Initiated (Manual)
Set callingEnabled: true and provide callbacks:
EnxChatWidget(
config: config,
showCallingOptions: true,
onAudioCall: () => YourCallSDK.startAudio(),
onVideoCall: () => YourCallSDK.startVideo(),
)
Plugin Internal Architecture
JS Bridge Communication
The bridge uses Flutter's JavascriptChannel API (named "EnxChannel"). All messages are JSON strings with the shape:
{
"event": "message",
"data": { ... }
}
Events Received from JS
| Event | Data | Description |
|---|---|---|
chatReady | — | HTML fully loaded |
connected | — | Socket connected |
disconnected | — | Socket dropped |
reconnecting | — | Auto-reconnect started |
reconnected | — | Reconnect succeeded |
reconnectFailed | — | Reconnect failed |
botInfo | Map<String, dynamic> | Bot metadata |
conversationReady | { conversationId: string } | Conversation initialised |
message | Array<MessageObject> | One or more messages |
typing | { duration: number } | Typing indicator |
clear | { conversationId?: string } | Conversation cleared |
messageSent | — | User message confirmed by server |
fileSent | — | File upload confirmed |
error | { message: string, type: string } | Error from JS |
Commands Sent to JS
window.sendTextMessage(text)
window.sendSuggestion(value)
window.sendMultiSuggestions([value1, value2])
window.sendFileMessage(base64DataURI, fileName, mimeType)
window.resetConversation()
window.reconnect()
window.disconnect()
Platform-Specific WebView Setup
The plugin automatically applies platform-specific WebView configuration via enx_webview_media_config.dart:
Android
PlatformWebViewControllerCreationParams enxWebViewCreationParamsForMedia()
// Returns AndroidWebViewControllerCreationParams with:
// - allowFileAccess: true
// - javaScriptCanOpenWindowsAutomatically: true
void enxConfigureAndroidWebViewMediaPermissions(WebViewController controller)
// Configures:
// - mediaPlaybackRequiresUserGesture: false
// - onPermissionRequest: auto-grant camera/mic
iOS
// Returns WebKitWebViewControllerCreationParams with:
// - allowsInlineMediaPlayback: true
// - mediaTypesRequiringUserActionForPlayback: []
File Structure
lib/
├── dialogs_chatbot_flutter.dart ← Single import barrel
└── src/
├── models/
│ ├── enx_bot_config.dart ← Bot configuration data object
│ └── enx_message.dart ← All message models + enums
├── controller/
│ └── enx_chat_controller.dart ← State, streams, send methods
├── bridge/
│ └── enx_webview_bridge.dart ← JS↔Dart bridge
├── utils/
│ └── enx_webview_media_config.dart ← Platform WebView config
└── views/
├── enx_chat_widget.dart ← Embeddable widget
├── enx_chat_screen.dart ← Full-screen scaffold
├── enx_chat_list.dart ← Scrollable message list
├── enx_message_cell.dart ← Per-message renderer + theme
├── enx_input_bar.dart ← Input + attachments + call buttons
├── enx_typing_bubble.dart ← Animated 3-dot indicator
└── enx_video_call_overlay.dart ← Agent video WebView (InAppWebView)
Feature Support (v1.0.0)
| Feature | Supported | Notes |
|---|---|---|
| Full chat UI | Yes | Message list (EnxChatList) and input bar (EnxInputBar), bot avatar/name from the bridge's botInfo payload, animated typing indicator. |
| Theme customization | Yes | Colour tokens via EnxChatTheme, including a built-in mobileClassic (WhatsApp-style) layout. |
| Text messages | Yes | Plain text, optionally with action link buttons. |
| Suggestion chips (single/multi-select) | Yes | |
| Image / video / file attachments | Yes | Image cards, video cards with thumbnail + play overlay, file attachment cards; sent via image_picker / file_picker. |
| Calendar (date picker) prompts | Yes | |
| System / handover status messages | Yes | Rendered as centered status messages. |
| Click-to-call (text / audio / video) | Yes | Server-driven via botInfo.clickToCall — full chat UI for “text”, a call-only UI otherwise (showClickToCallVideoOnlyUi). |
| Full-screen video call overlay | Yes | EnxVideoCallOverlay via flutter_inappwebview. |
| Conversation reset | Yes | EnxChatController.resetConversation(). |
| Reactive streams (BLoC-style) | Yes | messageStream / statusStream on EnxChatController. |
| Framework-agnostic state management | Yes | Works with Provider, Riverpod, BLoC, GetX, or plain setState via the external-controller pattern. |
| Web / desktop targets | No | iOS and Android only — use the JS SDK directly for web. |
| UI theming beyond color tokens | No | No layout/structural customization API in v1.0. |
| Offline message queue | No | Messages fail silently when disconnected; monitor statusStream and prompt retry. |
Dependencies
| Package | Version | License | Purpose |
|---|---|---|---|
webview_flutter | ^4.13.0 | BSD-3 | Chat bridge WebView |
flutter_inappwebview | ^6.1.5 | Apache-2.0 | Video call WebView |
image_picker | ^1.2.0 | BSD-3 | Camera/gallery |
file_picker | ^11.0.2 | MIT | Document picker |
permission_handler | ^12.0.1 | MIT | Runtime permissions |
cached_network_image | ^3.4.0 | MIT | Image caching |
video_player | ^2.11.0 | BSD-3 | Inline video |
url_launcher | ^6.3.0 | BSD-3 | Open action URLs |
http | ^1.6.0 | BSD-3 | HTTP requests |
mime | ^2.0.0 | BSD-3 | MIME detection |
intl | ^0.20.2 | BSD-3 | Date formatting |
Known Limitations
| Limitation | Impact | Workaround |
|---|---|---|
| iOS & Android only | Web/Desktop not supported | Use the JS SDK directly for web |
flutter_inappwebview is a peer dependency | Host app must add it | Add to host pubspec.yaml |
| Calendar message type not yet implemented | EnxMessageType.calendar renders as text | Planned for v1.1.0 |
| No offline queue | Messages fail silently when disconnected | Monitor statusStream and prompt retry |
| Android WebView version | Requires Android System WebView 67+ | Advise users to update |
Troubleshooting
Chat Shows Blank / Spinner Never Resolves
- Check that
botIdandhostare correct - Confirm network connectivity to
host - Check
onErrorcallback for bridge errors
Images/Videos Don't Autoplay on Android
- Ensure
enxConfigureAndroidWebViewMediaPermissionswas called (done automatically via bridge) - Check that
minSdkVersion >= 21
MissingPluginException for flutter_inappwebview
- Add
flutter_inappwebview: ^6.1.5to the host app'spubspec.yaml - Run
flutter pub getand rebuild
File Upload Fails on iOS
- Add all three
NS*UsageDescriptionkeys toInfo.plist - Request permissions before calling
sendFile
Black Screen in Video Overlay
- Confirm the
iFrameUrlis HTTPS - Add
NSAppTransportSecurityexception if needed for development
License
This plugin is proprietary software owned by EnableX / VCloudX Infotech. All rights reserved. Redistribution requires written permission.
Support
- Dashboard: portal.enablex.io
- Docs: developer.enablex.io
- Issues: Contact EnableX support team