clotpulse_flutter 2.0.1
clotpulse_flutter: ^2.0.1 copied to clipboard
Official Flutter SDK for ClotPulse realtime messaging. Realtime channels, FCM push notifications, and active-chat suppression on Android, iOS, and Web.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:clotpulse_flutter/clotpulse_flutter.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ClotPulse Example',
theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
home: const ChatScreen(),
);
}
}
class ChatScreen extends StatefulWidget {
const ChatScreen({super.key});
@override
State<ChatScreen> createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
late final ClotPulse _cp;
late final ClotPulsePublisher _publisher;
final _messages = <ClotPulseMessage>[];
final _controller = TextEditingController();
String _status = 'Connecting...';
@override
void initState() {
super.initState();
_setup();
}
Future<void> _setup() async {
_cp = ClotPulse(
host: 'https://api.clotpulse.app',
appId: const String.fromEnvironment('CP_APP_ID'),
key: const String.fromEnvironment('CP_API_KEY'),
);
_publisher = ClotPulsePublisher(
appId: const String.fromEnvironment('CP_APP_ID'),
secretKey: const String.fromEnvironment('CP_SECRET_KEY'),
);
_cp.connection.on.listen((change) {
if (!mounted) return;
setState(() {
_status = switch (change.current) {
ConnectionState.connected => 'Connected ✓',
ConnectionState.connecting => 'Connecting...',
ConnectionState.disconnected => 'Reconnecting...',
ConnectionState.failed => 'Connection failed',
ConnectionState.initialized => 'Initializing...',
};
});
});
try {
await _cp.connect();
} catch (e) {
if (mounted) setState(() => _status = 'Error: $e');
return;
}
// Subscribe to channel
final ch = _cp.channels.get('chat:1');
// Listen to all 'message' events
ch.subscribe('message').listen((msg) {
if (!mounted) return;
setState(() => _messages.add(msg));
});
}
Future<void> _send(String text) async {
if (text.trim().isEmpty) return;
_controller.clear();
try {
await _publisher.publish('chat:1', 'message', {
'text': text,
'sender': 'me',
});
} on ClotPulsePublishException catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Send failed: $e')),
);
}
}
}
@override
void dispose() {
_cp.close();
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('ClotPulse Chat'),
subtitle: Text(_status, style: const TextStyle(fontSize: 12)),
),
body: Column(
children: [
Expanded(
child: _messages.isEmpty
? const Center(child: Text('No messages yet'))
: ListView.builder(
padding: const EdgeInsets.all(12),
itemCount: _messages.length,
itemBuilder: (_, i) {
final msg = _messages[i];
final isMe = (msg.data as Map?)?['sender'] == 'me';
return Align(
alignment: isMe
? Alignment.centerRight
: Alignment.centerLeft,
child: Container(
margin: const EdgeInsets.symmetric(vertical: 4),
padding: const EdgeInsets.symmetric(
horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: isMe
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.surfaceVariant,
borderRadius: BorderRadius.circular(16),
),
child: Text(
(msg.data as Map?)?['text']?.toString() ?? '',
style: TextStyle(
color: isMe ? Colors.white : null,
),
),
),
);
},
),
),
Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
textInputAction: TextInputAction.send,
onSubmitted: _send,
decoration: InputDecoration(
hintText: 'Type a message...',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(24)),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 10),
),
),
),
const SizedBox(width: 8),
FilledButton(
onPressed: () => _send(_controller.text),
child: const Icon(Icons.send),
),
],
),
),
],
),
);
}
}