supabase_chat_sdk 0.1.0
supabase_chat_sdk: ^0.1.0 copied to clipboard
Offline-first Flutter chat SDK built on Supabase and PowerSync with real-time messaging, channels, reactions, and file uploads.
/// Minimal example demonstrating supabase_chat_sdk usage.
///
/// This example shows:
/// - SDK initialization
/// - User authentication flow
/// - Watching channels and messages
/// - Sending messages
///
/// For a complete showcase with UI components, see the root example/ app.
library;
import 'package:flutter/material.dart';
import 'package:supabase_chat_sdk/supabase_chat_sdk.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize the Chat SDK
await ChatClient.instance.initialize(
supabaseUrl: 'YOUR_SUPABASE_URL',
supabaseAnonKey: 'YOUR_SUPABASE_ANON_KEY',
powerSyncUrl: 'YOUR_POWERSYNC_URL',
);
runApp(const SDKExampleApp());
}
class SDKExampleApp extends StatelessWidget {
const SDKExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Chat SDK Example',
theme: ThemeData(useMaterial3: true),
home: const ChatExample(),
);
}
}
class ChatExample extends StatefulWidget {
const ChatExample({super.key});
@override
State<ChatExample> createState() => _ChatExampleState();
}
class _ChatExampleState extends State<ChatExample> {
final _client = ChatClient.instance;
bool _isConnected = false;
String? _selectedChannelId;
@override
void initState() {
super.initState();
_checkConnection();
}
Future<void> _checkConnection() async {
// Check if already authenticated
if (_client.supabase.auth.currentUser != null) {
await _client.connectUser();
setState(() => _isConnected = true);
}
}
Future<void> _signIn() async {
// Demo: Sign in with email/password
// In production, implement proper auth flow
try {
await _client.supabase.auth.signInWithPassword(
email: 'test@example.com',
password: 'password123',
);
await _client.connectUser();
setState(() => _isConnected = true);
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Sign in failed: $e')),
);
}
}
}
@override
Widget build(BuildContext context) {
if (!_isConnected) {
return Scaffold(
appBar: AppBar(title: const Text('SDK Example')),
body: Center(
child: ElevatedButton(
onPressed: _signIn,
child: const Text('Sign In'),
),
),
);
}
return Scaffold(
appBar: AppBar(
title: const Text('SDK Example'),
actions: [
IconButton(
icon: const Icon(Icons.logout),
onPressed: () async {
await _client.signOut();
setState(() => _isConnected = false);
},
),
],
),
body: Row(
children: [
// Channel list
SizedBox(
width: 250,
child: _buildChannelList(),
),
const VerticalDivider(width: 1),
// Messages
Expanded(
child: _selectedChannelId != null
? _buildMessageList(_selectedChannelId!)
: const Center(child: Text('Select a channel')),
),
],
),
);
}
Widget _buildChannelList() {
return StreamBuilder<List<Channel>>(
stream: _client.channels.watchChannels(),
builder: (context, snapshot) {
final channels = snapshot.data ?? [];
return ListView.builder(
itemCount: channels.length,
itemBuilder: (context, index) {
final channel = channels[index];
final isSelected = channel.id == _selectedChannelId;
return ListTile(
selected: isSelected,
leading: Icon(channel.isDirect ? Icons.person : Icons.group),
title: Text(channel.displayName ?? 'Channel'),
onTap: () => setState(() => _selectedChannelId = channel.id),
);
},
);
},
);
}
Widget _buildMessageList(String channelId) {
return Column(
children: [
Expanded(
child: StreamBuilder<List<Message>>(
stream: _client.messages.watchMessages(channelId),
builder: (context, snapshot) {
final messages = snapshot.data ?? [];
return ListView.builder(
reverse: true,
itemCount: messages.length,
itemBuilder: (context, index) {
final message = messages[index];
final isMe = message.userId == _client.currentUserId;
return Align(
alignment: isMe ? Alignment.centerRight : Alignment.centerLeft,
child: Container(
margin: const EdgeInsets.all(4),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: isMe ? Colors.blue : Colors.grey.shade300,
borderRadius: BorderRadius.circular(8),
),
child: Text(
message.content ?? '',
style: TextStyle(color: isMe ? Colors.white : Colors.black),
),
),
);
},
);
},
),
),
_MessageInput(
onSend: (text) => _client.messages.sendMessage(
channelId: channelId,
content: text,
),
),
],
);
}
}
class _MessageInput extends StatefulWidget {
final void Function(String) onSend;
const _MessageInput({required this.onSend});
@override
State<_MessageInput> createState() => _MessageInputState();
}
class _MessageInputState extends State<_MessageInput> {
final _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _send() {
final text = _controller.text.trim();
if (text.isEmpty) return;
widget.onSend(text);
_controller.clear();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'Type a message...',
border: OutlineInputBorder(),
),
onSubmitted: (_) => _send(),
),
),
IconButton(icon: const Icon(Icons.send), onPressed: _send),
],
),
);
}
}