uchara_sdk 1.0.2
uchara_sdk: ^1.0.2 copied to clipboard
Official Flutter SDK for the Uchara Chat Platform. Headless customer/visitor SDK for embedding realtime chat into Flutter apps: session init, conversations, messages, file uploads, transcript download [...]
example/lib/main.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:uchara_sdk/uchara_sdk.dart';
/// Runnable example for the Uchara Flutter SDK.
///
/// Demonstrates the full headless lifecycle:
/// init -> conversation -> messages -> realtime -> dispose
///
/// No real credentials are hardcoded. Enter your API URL and public widget
/// token in the UI (or set the `UCHARA_API_URL` / `UCHARA_WIDGET_TOKEN`
/// environment variables) before tapping "Run demo".
void main() {
runApp(const UcharaExampleApp());
}
class UcharaExampleApp extends StatelessWidget {
const UcharaExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Uchara SDK Example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
),
home: const DemoPage(),
);
}
}
class DemoPage extends StatefulWidget {
const DemoPage({super.key});
@override
State<DemoPage> createState() => _DemoPageState();
}
class _DemoPageState extends State<DemoPage> {
final _apiUrlController = TextEditingController(
text: const String.fromEnvironment('UCHARA_API_URL',
defaultValue: 'https://api.uchara.com'),
);
final _widgetTokenController = TextEditingController(
text: const String.fromEnvironment('UCHARA_WIDGET_TOKEN',
defaultValue: 'YOUR_PUBLIC_WIDGET_TOKEN'),
);
final _log = <String>[];
final _scrollController = ScrollController();
VisitorSDK? _sdk;
StreamSubscription<WSEvent>? _eventsSub;
bool _running = false;
@override
void dispose() {
_eventsSub?.cancel();
_sdk?.dispose();
_apiUrlController.dispose();
_widgetTokenController.dispose();
_scrollController.dispose();
super.dispose();
}
void _logLine(String line) {
setState(() => _log.add(line));
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_scrollController.hasClients) {
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
}
});
}
Future<void> _runDemo() async {
if (_running) return;
setState(() => _running = true);
_log.clear();
final apiUrl = _apiUrlController.text.trim();
final widgetToken = _widgetTokenController.text.trim();
if (widgetToken.isEmpty || widgetToken == 'YOUR_PUBLIC_WIDGET_TOKEN') {
_logLine('⚠️ Enter a real public widget token to run the demo.');
setState(() => _running = false);
return;
}
// Clean up any previous run.
_eventsSub?.cancel();
_sdk?.dispose();
final sdk = VisitorSDK(VisitorConfig(
apiUrl: apiUrl,
widgetToken: widgetToken,
identity: const VisitorIdentity(
externalId: 'example-user',
name: 'Example Visitor',
email: 'visitor@example.com',
),
autoConnect: true,
));
_sdk = sdk;
try {
_logLine('1. init() — exchanging widget token for a visitor session…');
final session = await sdk.init();
_logLine(' visitor token: ${_mask(session.visitorToken)}');
_logLine(' contact id: ${session.contactId}');
_logLine('2. Subscribing to realtime events…');
_eventsSub = sdk.events?.listen((event) {
_logLine(' [realtime] ${event.type}');
});
_logLine('3. getActiveConversation()…');
var conversation = await sdk.getActiveConversation();
if (conversation == null) {
_logLine(' No active conversation — starting a new one.');
conversation =
await sdk.startConversation(message: 'Hello from Flutter!');
}
_logLine(' conversation id: ${conversation.id}');
_logLine('4. sendMessage()…');
final sent = await sdk.sendMessage(
conversation.id,
content: 'Is anyone there?',
);
_logLine(' sent message id: ${sent.id}');
_logLine('5. getMessages() (paginated)…');
final page = await sdk.getMessages(conversation.id, limit: 50);
_logLine(' ${page.messages.length} message(s) returned');
_logLine('6. sendTyping() over the realtime connection…');
sdk.sendTyping(conversation.id);
_logLine(' typing indicator sent');
_logLine('7. dispose() — releasing the SDK…');
await _eventsSub?.cancel();
_eventsSub = null;
sdk.dispose();
_sdk = null;
_logLine(' done. ✅');
} on ServerEnvelopeException catch (e) {
_logLine(
'❌ ServerEnvelopeException(${e.statusCode}, ${e.code}): ${e.message}');
} on ApiException catch (e) {
_logLine('❌ ApiException(${e.statusCode}): ${e.message}');
} on NetworkException catch (e) {
_logLine('❌ NetworkException: ${e.message}');
} on UcharaException catch (e) {
_logLine('❌ UcharaException: ${e.message}');
} finally {
setState(() => _running = false);
}
}
String _mask(String token) {
if (token.length <= 8) return '***';
return '${token.substring(0, 4)}…${token.substring(token.length - 4)}';
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Uchara SDK Example')),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
TextField(
controller: _apiUrlController,
decoration: const InputDecoration(
labelText: 'API URL',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
TextField(
controller: _widgetTokenController,
decoration: const InputDecoration(
labelText: 'Public widget token',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: FilledButton(
onPressed: _running ? null : _runDemo,
child: Text(_running ? 'Running…' : 'Run demo'),
),
),
],
),
),
const Divider(height: 1),
Expanded(
child: ListView.builder(
controller: _scrollController,
padding: const EdgeInsets.all(12),
itemCount: _log.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Text(
_log[index],
style:
const TextStyle(fontFamily: 'monospace', fontSize: 13),
),
);
},
),
),
],
),
);
}
}