supabase_chat_sdk 0.1.0 copy "supabase_chat_sdk: ^0.1.0" to clipboard
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.

example/lib/main.dart

/// 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),
        ],
      ),
    );
  }
}
0
likes
140
points
122
downloads

Publisher

unverified uploader

Weekly Downloads

Offline-first Flutter chat SDK built on Supabase and PowerSync with real-time messaging, channels, reactions, and file uploads.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, path, path_provider, powersync, supabase_flutter, uuid

More

Packages that depend on supabase_chat_sdk