flutter_chat_io 2.3.0
flutter_chat_io: ^2.3.0 copied to clipboard
Live customer support for Flutter apps with Slack integration. Users chat in-app, your team responds in Slack.
example/lib/main.dart
// ignore_for_file: public_member_api_docs
// PUSH NOTIFICATIONS — IN-PROGRESS
// Firebase imports are disabled until push-notification support is finished.
// import 'package:firebase_core/firebase_core.dart';
// import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_chat_io/flutter_chat_io.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
/// Example application demonstrating FlutterChatIO integration.
///
/// This example shows:
/// - SDK initialization
/// - User identification
/// - Theme customization
/// - Navigation to chat widget
/// - Push notifications with Firebase (optional) — IN-PROGRESS, currently disabled
// PUSH NOTIFICATIONS — IN-PROGRESS
// Background FCM handler is disabled until push-notification support is finished.
/*
/// Background message handler for Firebase Cloud Messaging.
/// This must be a top-level function.
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
// ignore: avoid_print
print('Background message: ${message.messageId}');
}
*/
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// PUSH NOTIFICATIONS — IN-PROGRESS
// Firebase init is disabled until push-notification support is finished.
/*
// Initialize Firebase (EXAMPLE ONLY - not required for SDK)
try {
await Firebase.initializeApp();
// Set up background message handler
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
} catch (e) {
// Firebase initialization failed - this is OK for testing without Firebase
// ignore: avoid_print
print('Firebase initialization failed: $e');
}
*/
// Initialize FlutterChatIO with your credentials
// Get your client ID and token from https://flutterchat.io/account/dashboard
FlutterChatIO.initialize(
clientId: 'user_41d920be-40e1-7082-4688-b3dbd30427b6',
clientToken: '0afa2999099921f3a08b4ad7a0dc35f5b7bdbdbb08a7bc6413bcfe7a7de1768b',
);
runApp(const FlutterChatIOExampleApp());
}
/// Root application widget.
class FlutterChatIOExampleApp extends StatelessWidget {
const FlutterChatIOExampleApp({super.key});
@override
Widget build(BuildContext context) => MaterialApp(
title: 'FlutterChatIO Example',
// Add localization support for RTL languages (Arabic, Hebrew, etc.)
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('en'), // English
Locale('zh'), // Chinese
Locale('es'), // Spanish
Locale('ar'), // Arabic (RTL)
Locale('hi'), // Hindi
Locale('ru'), // Russian
],
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.indigo,
brightness: Brightness.dark,
),
useMaterial3: true,
),
home: const HomePage(),
);
}
/// Home page with options to open different themed chats.
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
// User Info controllers
final _userIdController = TextEditingController(text: 'demo-user-123');
final _emailController = TextEditingController(text: 'demo@example.com');
final _nameController = TextEditingController(text: 'Demo User');
// Custom User Data controllers (key-value pairs)
final _subscriptionController = TextEditingController(text: 'premium');
final _accountTypeController = TextEditingController(text: 'business');
final _signupDateController = TextEditingController(text: '2024-01-15');
@override
void initState() {
super.initState();
// PUSH NOTIFICATIONS — IN-PROGRESS
// _setupNotifications() is disabled until push-notification support is finished.
// _setupNotifications();
}
@override
void dispose() {
_userIdController.dispose();
_emailController.dispose();
_nameController.dispose();
_subscriptionController.dispose();
_accountTypeController.dispose();
_signupDateController.dispose();
super.dispose();
}
// PUSH NOTIFICATIONS — IN-PROGRESS
// Firebase Cloud Messaging wiring is disabled until push-notification support
// is finished. Re-enable this block together with the Firebase imports, the
// Firebase init in `main()`, the `_setupNotifications()` call in
// `initState()`, and the dependencies in `pubspec.yaml`.
/*
/// Sets up Firebase Cloud Messaging for push notifications.
///
/// This demonstrates the delegate pattern - the SDK doesn't handle
/// Firebase setup, the host app does.
Future<void> _setupNotifications() async {
try {
final messaging = FirebaseMessaging.instance;
// Request notification permissions (iOS)
final settings = await messaging.requestPermission(
alert: true,
badge: true,
sound: true,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
// ignore: avoid_print
print('User granted notification permission');
// Get FCM token
final token = await messaging.getToken();
if (token != null) {
// ignore: avoid_print
print('FCM Token: $token');
// Register token via HTTP - works even if chat is never opened
FlutterChatIO.setNotificationToken(token: token);
}
// Listen for token refresh
messaging.onTokenRefresh.listen((newToken) {
// ignore: avoid_print
print('Token refreshed: $newToken');
FlutterChatIO.setNotificationToken(token: newToken);
});
// Handle foreground messages
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
// ignore: avoid_print
print('🔔 FOREGROUND MESSAGE RECEIVED');
// ignore: avoid_print
print(' Notification: ${message.notification?.title}');
// ignore: avoid_print
print(' Body: ${message.notification?.body}');
// ignore: avoid_print
print(' Data: ${message.data}');
if (FlutterChatIO.isFlutterChatIONotification(message.data)) {
// ignore: avoid_print
print('✅ This is a FlutterChatIO notification!');
// Show in-app notification or update chat UI
_showInAppNotification(message);
} else {
// ignore: avoid_print
print(
'❌ NOT a FlutterChatIO notification - data.source = ${message.data['source']}');
}
});
// Handle notification taps (when app is in background)
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
// ignore: avoid_print
print('🔔 NOTIFICATION TAPPED (from background)');
// ignore: avoid_print
print(' Data: ${message.data}');
if (FlutterChatIO.isFlutterChatIONotification(message.data)) {
// ignore: avoid_print
print('✅ Opening FlutterChatIO chat');
// Navigate to chat if widget is still mounted
if (mounted) {
Navigator.push(
context,
MaterialPageRoute<void>(
builder: (_) => const FlutterChatIOChat(),
),
);
}
}
});
}
} catch (e) {
// Firebase not configured - this is OK
// ignore: avoid_print
print('Firebase notification setup failed: $e');
}
}
/// Shows an in-app notification when a message is received while app is open.
void _showInAppNotification(RemoteMessage message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message.notification?.body ?? 'New message'),
action: SnackBarAction(
label: 'Open',
onPressed: () {
Navigator.push(
context,
MaterialPageRoute<void>(
builder: (_) => const FlutterChatIOChat(),
),
);
},
),
behavior: SnackBarBehavior.floating,
),
);
}
*/
/// Builds custom user data map from controllers, excluding empty values.
Map<String, String> _buildUserData() {
final data = <String, String>{};
if (_subscriptionController.text.isNotEmpty) {
data['subscription'] = _subscriptionController.text;
}
if (_accountTypeController.text.isNotEmpty) {
data['accountType'] = _accountTypeController.text;
}
if (_signupDateController.text.isNotEmpty) {
data['signupDate'] = _signupDateController.text;
}
return data;
}
void _updateUserInfo() {
FlutterChatIO.setUserId(userId: _userIdController.text);
// Set user info and custom data in a single call
// Note: Device info is automatically detected by the SDK
final customData = _buildUserData();
FlutterChatIO.setUserInfo(
email: _emailController.text,
userName: _nameController.text,
customData: customData.isNotEmpty ? customData : null,
);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('User info updated'),
behavior: SnackBarBehavior.floating,
),
);
}
void _openChat(FlutterChatIOTheme theme, [Locale? locale]) {
// Ensure user info is set before opening chat
FlutterChatIO.setUserId(userId: _userIdController.text);
// Set user info and custom data in a single call
// Note: Device info is automatically detected by the SDK
final customData = _buildUserData();
FlutterChatIO.setUserInfo(
email: _emailController.text,
userName: _nameController.text,
customData: customData.isNotEmpty ? customData : null,
);
Navigator.push(
context,
MaterialPageRoute<void>(
builder: (_) {
final chat = FlutterChatIOChat(theme: theme);
// Wrap with locale override for RTL support
if (locale != null) {
return Localizations.override(
context: context,
locale: locale,
child: Directionality(
textDirection: _isRtlLocale(locale)
? TextDirection.rtl
: TextDirection.ltr,
child: chat,
),
);
}
return chat;
},
),
);
}
/// Returns true if the locale uses Right-to-Left text direction.
bool _isRtlLocale(Locale locale) {
const rtlLanguages = ['ar', 'he', 'fa', 'ur'];
return rtlLanguages.contains(locale.languageCode);
}
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: const Text('FlutterChatIO Example'),
centerTitle: true,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// User Configuration Section
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'User Configuration',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 16),
TextField(
controller: _userIdController,
decoration: const InputDecoration(
labelText: 'User ID',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.person),
),
),
const SizedBox(height: 12),
TextField(
controller: _emailController,
decoration: const InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.email),
),
),
const SizedBox(height: 12),
TextField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'Display Name',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.badge),
),
),
const SizedBox(height: 20),
// Custom User Data Section
Row(
children: [
Text(
'Custom User Data',
style: Theme.of(context).textTheme.titleSmall,
),
const SizedBox(width: 8),
Tooltip(
message:
'Key-value pairs sent to your support team',
child: Icon(
Icons.info_outline,
size: 16,
color: Theme.of(context).colorScheme.outline,
),
),
],
),
const SizedBox(height: 12),
TextField(
controller: _subscriptionController,
decoration: const InputDecoration(
labelText: 'Subscription',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.card_membership),
hintText: 'e.g., premium, free, enterprise',
),
),
const SizedBox(height: 12),
TextField(
controller: _accountTypeController,
decoration: const InputDecoration(
labelText: 'Account Type',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.business),
hintText: 'e.g., business, personal',
),
),
const SizedBox(height: 12),
TextField(
controller: _signupDateController,
decoration: const InputDecoration(
labelText: 'Signup Date',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.calendar_today),
hintText: 'e.g., 2024-01-15',
),
),
const SizedBox(height: 16),
FilledButton.icon(
onPressed: _updateUserInfo,
icon: const Icon(Icons.save),
label: const Text('Save User Info'),
),
],
),
),
),
const SizedBox(height: 24),
// Theme Examples Section
Text(
'Theme Examples',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 12),
// Default Theme
_ThemeCard(
title: 'Default Theme',
subtitle: 'Material Blue styling',
color: const Color(0xFF1976D2),
onTap: () => _openChat(const FlutterChatIOTheme()),
),
// Indigo Theme
_ThemeCard(
title: 'Indigo Theme',
subtitle: 'Purple/indigo accent colors',
color: Colors.indigo,
onTap: () => _openChat(
FlutterChatIOTheme(
appBarColor: Colors.indigo,
appBarTitle: 'Help Center',
outgoingMessageColor: Colors.indigo,
sendButtonColor: Colors.indigo,
emptyStateTitle: 'How can we help?',
emptyStateSubtitle: 'Our team is here to assist you',
),
),
),
// Teal Theme
_ThemeCard(
title: 'Teal Theme',
subtitle: 'Fresh teal accent colors',
color: Colors.teal,
onTap: () => _openChat(
FlutterChatIOTheme(
appBarColor: Colors.teal,
appBarTitle: 'Support Chat',
outgoingMessageColor: Colors.teal,
sendButtonColor: Colors.teal,
onlineColor: Colors.teal,
emptyStateTitle: 'Welcome!',
emptyStateSubtitle: 'Start a conversation with us',
),
),
),
// Dark Theme
_ThemeCard(
title: 'Dark Theme',
subtitle: 'Dark mode styling',
color: const Color(0xFF2D2D2D),
onTap: () => _openChat(
FlutterChatIOTheme(
// AppBar
appBarColor: const Color(0xFF1E1E1E),
appBarTitle: 'Night Support',
// Background
backgroundColor: const Color(0xFF121212),
// Messages
incomingMessageColor: const Color(0xFF2D2D2D),
outgoingMessageColor: Colors.indigo,
incomingMessageTextStyle: const TextStyle(
fontSize: 15,
color: Colors.white,
),
incomingTimeTextStyle: const TextStyle(
fontSize: 11,
color: Colors.white54,
),
// Input
inputBackgroundColor: const Color(0xFF2D2D2D),
inputTextColor: Colors.white,
inputHintColor: Colors.white54,
keyboardAppearance: Brightness.dark,
sendButtonColor: Colors.indigo,
// Empty state
emptyStateTitle: 'Night Owl Support',
emptyStateSubtitle: 'We are here 24/7',
emptyStateTitleStyle: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Colors.white,
),
emptyStateSubtitleStyle: const TextStyle(
fontSize: 14,
color: Colors.white70,
),
),
),
),
// Fully Customized Theme — showcases new v2.4.0 fields:
// backButtonIcon / backButtonColor, attachmentIconWidget /
// attachmentIconColor, inputHintText / inputHintStyle /
// inputTextStyle, cursorColor.
_ThemeCard(
title: 'Fully Customized',
subtitle: 'New input & back button options (v2.4.0)',
color: Colors.deepPurple,
onTap: () => _openChat(
FlutterChatIOTheme(
appBarColor: Colors.deepPurple,
appBarTitle: 'Concierge',
outgoingMessageColor: Colors.deepPurple,
sendButtonColor: Colors.deepPurple,
// Back button — custom icon + amber tint
backButtonIcon: const Icon(Icons.arrow_back_ios_new),
backButtonColor: Colors.amberAccent,
// Composer — custom placeholder and typography
inputHintText: 'Ask us anything…',
inputHintStyle: const TextStyle(
fontSize: 14,
fontStyle: FontStyle.italic,
),
inputTextStyle: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
),
cursorColor: Colors.deepPurpleAccent,
// Attachment — custom icon widget, color decoupled
// from the app bar
attachmentIconWidget:
const Icon(Icons.attach_file_rounded),
attachmentIconColor: Colors.grey,
),
),
),
const SizedBox(height: 24),
// Auto-Localization Section
Text(
'Auto-Localized Themes',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
Text(
'Uses FlutterChatIOTheme.localized() for automatic translation',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 12),
// Device Locale (Auto-detect)
_ThemeCard(
title: 'Device Locale',
subtitle: 'Auto-detect from device settings',
color: Colors.deepPurple,
onTap: () => _openChat(
FlutterChatIOTheme.localized(
Localizations.localeOf(context),
appBarColor: Colors.deepPurple,
outgoingMessageColor: Colors.deepPurple,
sendButtonColor: Colors.deepPurple,
),
),
),
// Chinese
_ThemeCard(
title: 'Chinese',
subtitle: '中文本地化',
color: Colors.red,
onTap: () => _openChat(
FlutterChatIOTheme.localized(
const Locale('zh'),
appBarColor: Colors.red,
outgoingMessageColor: Colors.red,
sendButtonColor: Colors.red,
),
),
),
// Spanish
_ThemeCard(
title: 'Spanish',
subtitle: 'Localización en español',
color: Colors.orange,
onTap: () => _openChat(
FlutterChatIOTheme.localized(
const Locale('es'),
appBarColor: Colors.orange,
outgoingMessageColor: Colors.orange,
sendButtonColor: Colors.orange,
),
),
),
// Arabic (RTL)
_ThemeCard(
title: 'Arabic (RTL)',
subtitle: 'التوطين العربي',
color: Colors.green,
onTap: () => _openChat(
FlutterChatIOTheme.localized(
const Locale('ar'),
appBarColor: Colors.green,
outgoingMessageColor: Colors.green,
sendButtonColor: Colors.green,
),
const Locale('ar'), // Enable RTL
),
),
// Hindi
_ThemeCard(
title: 'Hindi',
subtitle: 'हिंदी स्थानीयकरण',
color: Colors.amber.shade800,
onTap: () => _openChat(
FlutterChatIOTheme.localized(
const Locale('hi'),
appBarColor: Colors.amber.shade800,
outgoingMessageColor: Colors.amber.shade800,
sendButtonColor: Colors.amber.shade800,
),
),
),
const SizedBox(height: 24),
// SDK Info
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'SDK Information',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 12),
_InfoRow(
label: 'Initialized',
value: FlutterChatIO.isInitialized ? 'Yes' : 'No',
),
_InfoRow(
label: 'WebSocket Endpoint',
value: FlutterChatIO.apiWss,
),
_InfoRow(
label: 'HTTP Endpoint',
value: FlutterChatIO.apiHttpHistory,
),
],
),
),
),
],
),
),
);
}
/// A card displaying a theme option.
class _ThemeCard extends StatelessWidget {
const _ThemeCard({
required this.title,
required this.subtitle,
required this.color,
required this.onTap,
});
final String title;
final String subtitle;
final Color color;
final VoidCallback onTap;
@override
Widget build(BuildContext context) => Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
leading: Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(8),
),
child: const Icon(Icons.chat, color: Colors.white),
),
title: Text(title),
subtitle: Text(subtitle),
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
onTap: onTap,
),
);
}
/// A row displaying label-value information.
class _InfoRow extends StatelessWidget {
const _InfoRow({
required this.label,
required this.value,
});
final String label;
final String value;
@override
Widget build(BuildContext context) => Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 140,
child: Text(
label,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
Expanded(
child: Text(
value,
style: Theme.of(context).textTheme.bodyMedium,
),
),
],
),
);
}